OSDN Git Service

versoin1.1.9 (#594) master v1.1.9
authoryzb <335357057@qq.com>
Thu, 19 Aug 2021 02:36:56 +0000 (10:36 +0800)
committerGitHub <noreply@github.com>
Thu, 19 Aug 2021 02:36:56 +0000 (10:36 +0800)
Co-authored-by: yzb@example.cn <yzb@example.cn>
180 files changed:
api/nodeinfo.go
api/transact.go
application/mov/common/type.go
application/mov/database/mov_store.go
application/mov/database/mov_store_test.go
application/mov/match/engine.go
application/mov/match/engine_test.go
application/mov/match/fee_strategy.go
application/mov/mock/mock.go
application/mov/mov_core.go
application/mov/mov_core_test.go
blockchain/txbuilder/actions.go
cmd/vapord/commands/run_node.go
config/config.go
config/genesis.go
config/toml.go
consensus/general.go
crypto/csp/csp.go
docs/release-notes/release-notes-1.1.4.md [new file with mode: 0644]
log/log.go
netsync/chainmgr/block_keeper.go
netsync/chainmgr/block_process.go
netsync/chainmgr/handle.go
netsync/consensusmgr/handle.go
node/node.go
proposal/blockproposer/blockproposer.go
proposal/proposal.go
protocol/asset_filter.go
protocol/asset_filter_test.go [new file with mode: 0644]
protocol/bbft.go
protocol/block.go
protocol/consensus_node_manager.go
protocol/orphan_manage.go
protocol/protocol.go
protocol/tx.go
protocol/txpool_test.go
protocol/validation/block.go
protocol/validation/block_test.go
protocol/validation/tx.go
protocol/vm/vmutil/script.go
toolbar/apinode/block.go
toolbar/consensusreward/consensus_reward.go
toolbar/federation/service/node.go
toolbar/federation/synchron/sidechain_keeper.go
toolbar/measure/README.md [new file with mode: 0644]
toolbar/measure/method.go [new file with mode: 0644]
toolbar/measure/timer.go [new file with mode: 0644]
toolbar/osssync/Dockerfile [new file with mode: 0644]
toolbar/osssync/README.md [new file with mode: 0644]
toolbar/osssync/cmd/main.go [new file with mode: 0644]
toolbar/osssync/download/download.go [new file with mode: 0644]
toolbar/osssync/download/oss.go [new file with mode: 0644]
toolbar/osssync/upload/config.go [new file with mode: 0644]
toolbar/osssync/upload/oss.go [new file with mode: 0644]
toolbar/osssync/upload/upload.go [new file with mode: 0644]
toolbar/osssync/util/file.go [new file with mode: 0644]
toolbar/osssync/util/gzip.go [new file with mode: 0644]
toolbar/osssync/util/infofile.go [new file with mode: 0644]
toolbar/osssync/util/json.go [new file with mode: 0644]
toolbar/vote_reward/settlementvotereward/settlementreward.go
vendor/github.com/aliyun/aliyun-oss-go-sdk/.travis.yml [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/CHANGELOG.md [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/LICENSE [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/README-CN.md [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/README.md [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/auth.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket_credential_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conf.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/const.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/aes_ctr.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/aes_ctr_cipher.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/aes_ctr_cipher_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/cipher.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/cipher_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_bucket.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_bucket_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_const.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_download.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_multicopy.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_multipart.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_multipart_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_type.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_upload.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_alikms_cipher.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_alikms_cipher_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_rsa_cipher.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_rsa_cipher_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_6.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_7.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/mime.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/model.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/redirect_1_6.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/redirect_1_7.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_csv_objcet_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_json_object_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_object.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_object_read_file_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_object_type.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_6.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_7.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils_test.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/BingWallpaper-2015-11-07.jpg [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/The Go Programming Language.html [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/append_object.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/archive.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_acl.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_cors.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_encryption.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_inventory.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_lifecycle.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_logging.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_policy.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_qosInfo.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_referer.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_requestpayment.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_website.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/cname_sample.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/comm.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/config.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/copy_object.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/create_bucket.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/delete_object.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/get_object.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_buckets.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_objects.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/livechannel.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/new_bucket.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_acl.go [new file with mode: 0755]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_meta.go [new file with mode: 0755]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_tagging.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/put_object.go [new file with mode: 0755]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sample_data.csv [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sample_json.json [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sample_json_lines.json [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/select_object.go [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sign_url.go [new file with mode: 0755]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-crypto-cpp-rsa.jpg [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-crypto-python-rsa.jpg [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-src.jpg [new file with mode: 0644]
vendor/github.com/aliyun/aliyun-oss-go-sdk/sample_crypto/sample_crypto.go [new file with mode: 0644]
vendor/golang.org/x/time/AUTHORS [new file with mode: 0644]
vendor/golang.org/x/time/CONTRIBUTING.md [new file with mode: 0644]
vendor/golang.org/x/time/CONTRIBUTORS [new file with mode: 0644]
vendor/golang.org/x/time/LICENSE [new file with mode: 0644]
vendor/golang.org/x/time/PATENTS [new file with mode: 0644]
vendor/golang.org/x/time/README.md [new file with mode: 0644]
vendor/golang.org/x/time/go.mod [new file with mode: 0644]
vendor/golang.org/x/time/rate/rate.go [new file with mode: 0644]
vendor/golang.org/x/time/rate/rate_test.go [new file with mode: 0644]
version/version.go
wallet/annotated.go
wallet/utxo.go

index 6557e33..e377944 100644 (file)
@@ -24,16 +24,17 @@ type VersionInfo struct {
 
 // NetInfo indicate net information
 type NetInfo struct {
-       Listening    bool         `json:"listening"`
-       Syncing      bool         `json:"syncing"`
-       Mining       bool         `json:"mining"`
-       NodeXPub     string       `json:"node_xpub"`
-       FedAddress   string       `json:"federation_address"`
-       PeerCount    int          `json:"peer_count"`
-       CurrentBlock uint64       `json:"current_block"`
-       HighestBlock uint64       `json:"highest_block"`
-       NetWorkID    string       `json:"network_id"`
-       Version      *VersionInfo `json:"version_info"`
+       Listening         bool         `json:"listening"`
+       Syncing           bool         `json:"syncing"`
+       Mining            bool         `json:"mining"`
+       NodeXPub          string       `json:"node_xpub"`
+       FedAddress        string       `json:"federation_address"`
+       PeerCount         int          `json:"peer_count"`
+       CurrentBlock      uint64       `json:"current_block"`
+       IrreversibleBlock uint64       `json:"irreversible_block"`
+       HighestBlock      uint64       `json:"highest_block"`
+       NetWorkID         string       `json:"network_id"`
+       Version           *VersionInfo `json:"version_info"`
 }
 
 // GetNodeInfo return net information
@@ -48,14 +49,15 @@ func (a *API) GetNodeInfo() *NetInfo {
        }
 
        info := &NetInfo{
-               Listening:    a.sync.IsListening(),
-               Syncing:      !a.sync.IsCaughtUp(),
-               Mining:       a.blockProposer.IsProposing(),
-               NodeXPub:     nodeXPub.String(),
-               FedAddress:   address.EncodeAddress(),
-               PeerCount:    a.sync.PeerCount(),
-               CurrentBlock: a.chain.BestBlockHeight(),
-               NetWorkID:    a.sync.GetNetwork(),
+               Listening:         a.sync.IsListening(),
+               Syncing:           !a.sync.IsCaughtUp(),
+               Mining:            a.blockProposer.IsProposing(),
+               NodeXPub:          nodeXPub.String(),
+               FedAddress:        address.EncodeAddress(),
+               PeerCount:         a.sync.PeerCount(),
+               CurrentBlock:      a.chain.BestBlockHeight(),
+               IrreversibleBlock: a.chain.LastIrreversibleHeader().Height,
+               NetWorkID:         a.sync.GetNetwork(),
                Version: &VersionInfo{
                        Version: version.Version,
                        Update:  version.Status.VersionStatus(),
index 26719d3..eff58a3 100644 (file)
@@ -62,6 +62,7 @@ func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Te
        }
 
        maxTime := time.Now().Add(req.TTL.Duration)
+       ctx = context.WithValue(ctx, "block_height", a.wallet.Chain.BestBlockHeight())
        tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
        if errors.Root(err) == txbuilder.ErrAction {
                // append each of the inner errors contained in the data.
index 11d89bb..749eaff 100644 (file)
@@ -26,6 +26,9 @@ type Order struct {
        Utxo             *MovUtxo
        RatioNumerator   int64
        RatioDenominator int64
+       SellerProgram    []byte
+       BlockHeight      uint64
+       TxIndex          uint64
 }
 
 // Rate return the exchange represented by float64
@@ -67,7 +70,7 @@ func (o OrderSlice) Less(i, j int) bool {
 }
 
 // NewOrderFromOutput convert txinput to order
-func NewOrderFromOutput(tx *types.Tx, outputIndex int) (*Order, error) {
+func NewOrderFromOutput(tx *types.Tx, outputIndex int, blockHeight, txIndex uint64) (*Order, error) {
        outputID := tx.OutputID(outputIndex)
        output, err := tx.IntraChainOutput(*outputID)
        if err != nil {
@@ -85,6 +88,9 @@ func NewOrderFromOutput(tx *types.Tx, outputIndex int) (*Order, error) {
                ToAssetID:        &contractArgs.RequestedAsset,
                RatioNumerator:   contractArgs.RatioNumerator,
                RatioDenominator: contractArgs.RatioDenominator,
+               SellerProgram:    contractArgs.SellerProgram,
+               BlockHeight:      blockHeight,
+               TxIndex:          txIndex,
                Utxo: &MovUtxo{
                        SourceID:       output.Source.Ref,
                        Amount:         assetAmount.Amount,
@@ -111,6 +117,7 @@ func NewOrderFromInput(tx *types.Tx, inputIndex int) (*Order, error) {
                ToAssetID:        &contractArgs.RequestedAsset,
                RatioNumerator:   contractArgs.RatioNumerator,
                RatioDenominator: contractArgs.RatioDenominator,
+               SellerProgram:    contractArgs.SellerProgram,
                Utxo: &MovUtxo{
                        SourceID:       &input.SourceID,
                        Amount:         input.Amount,
index c45b46f..0ed5eca 100644 (file)
@@ -7,6 +7,7 @@ import (
        "math"
 
        "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/consensus/segwit"
        dbm "github.com/bytom/vapor/database/leveldb"
        "github.com/bytom/vapor/protocol/bc"
        "github.com/bytom/vapor/protocol/bc/types"
@@ -50,6 +51,8 @@ type orderData struct {
        Utxo             *common.MovUtxo
        RatioNumerator   int64
        RatioDenominator int64
+       BlockHeight      uint64
+       TxIndex          uint64
 }
 
 func calcOrderKey(fromAssetID, toAssetID *bc.AssetID, utxoHash *bc.Hash, rate float64) []byte {
@@ -152,12 +155,20 @@ func (m *LevelDBMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order,
                        return nil, err
                }
 
+               contractArgs, err := segwit.DecodeP2WMCProgram(orderData.Utxo.ControlProgram)
+               if err != nil {
+                       return nil, err
+               }
+
                orders = append(orders, &common.Order{
                        FromAssetID:      orderAfter.FromAssetID,
                        ToAssetID:        orderAfter.ToAssetID,
                        Utxo:             orderData.Utxo,
                        RatioNumerator:   orderData.RatioNumerator,
                        RatioDenominator: orderData.RatioDenominator,
+                       BlockHeight:      orderData.BlockHeight,
+                       TxIndex:          orderData.TxIndex,
+                       SellerProgram:    contractArgs.SellerProgram,
                })
        }
        return orders, nil
@@ -225,6 +236,8 @@ func (m *LevelDBMovStore) addOrders(batch dbm.Batch, orders []*common.Order, tra
                        Utxo:             order.Utxo,
                        RatioNumerator:   order.RatioNumerator,
                        RatioDenominator: order.RatioDenominator,
+                       BlockHeight:      order.BlockHeight,
+                       TxIndex:          order.TxIndex,
                }
                data, err := json.Marshal(orderData)
                if err != nil {
index aeb9c3f..022051f 100644 (file)
@@ -26,6 +26,9 @@ var (
        assetID6 = &bc.AssetID{V0: 6}
        assetID7 = &bc.AssetID{V0: 7}
        assetID8 = &bc.AssetID{V0: 8}
+       
+       orderProgram = testutil.MustDecodeHexString("0020184e1cc4ee4845023888810a79eed7a42c02c544cf2c61ceac05e176d575bd4603ed4e0e0210272200204775b9e167e2c1ffe57ae3e5088af69e518be010529b5fdadf4be97656084eec20a3e21b55f44403884457166ad5847fdb5489512ba9611eee466efb9f94319143")
+       sellerProgram = testutil.MustDecodeHexString("00204775b9e167e2c1ffe57ae3e5088af69e518be010529b5fdadf4be97656084eec")
 
        mockOrders = []*common.Order{
                &common.Order{
@@ -37,8 +40,9 @@ var (
                                SourceID:       &bc.Hash{V0: 21},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID1,
@@ -49,8 +53,9 @@ var (
                                SourceID:       &bc.Hash{V0: 22},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID1,
@@ -61,8 +66,9 @@ var (
                                SourceID:       &bc.Hash{V0: 23},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID1,
@@ -73,8 +79,9 @@ var (
                                SourceID:       &bc.Hash{V0: 13},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID1,
@@ -85,8 +92,9 @@ var (
                                SourceID:       &bc.Hash{V0: 24},
                                Amount:         10,
                                SourcePos:      1,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID1,
@@ -97,8 +105,9 @@ var (
                                SourceID:       &bc.Hash{V0: 24},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID1,
@@ -109,8 +118,9 @@ var (
                                SourceID:       &bc.Hash{V0: 25},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID1,
@@ -121,8 +131,9 @@ var (
                                SourceID:       &bc.Hash{V0: 26},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID1,
@@ -133,8 +144,9 @@ var (
                                SourceID:       &bc.Hash{V0: 1},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID1,
@@ -145,8 +157,9 @@ var (
                                SourceID:       &bc.Hash{V0: 2},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID3,
@@ -157,8 +170,9 @@ var (
                                SourceID:       &bc.Hash{V0: 33},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID4,
@@ -169,8 +183,9 @@ var (
                                SourceID:       &bc.Hash{V0: 34},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID4,
@@ -181,8 +196,9 @@ var (
                                SourceID:       &bc.Hash{V0: 36},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID5,
@@ -193,8 +209,9 @@ var (
                                SourceID:       &bc.Hash{V0: 37},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
                &common.Order{
                        FromAssetID:      assetID6,
@@ -205,8 +222,9 @@ var (
                                SourceID:       &bc.Hash{V0: 38},
                                Amount:         1,
                                SourcePos:      0,
-                               ControlProgram: []byte("aa"),
+                               ControlProgram: orderProgram,
                        },
+                       SellerProgram: sellerProgram,
                },
        }
 )
@@ -624,8 +642,8 @@ func TestMovStore(t *testing.T) {
                                mockOrders[7],
                                mockOrders[6],
                                mockOrders[2],
-                               mockOrders[3],
                                mockOrders[4],
+                               mockOrders[3],
                                mockOrders[5],
                                mockOrders[0],
                        },
@@ -663,8 +681,8 @@ func TestMovStore(t *testing.T) {
                        wantOrders: []*common.Order{
                                mockOrders[7],
                                mockOrders[6],
-                               mockOrders[3],
                                mockOrders[4],
+                               mockOrders[3],
                                mockOrders[5],
                        },
                        wantTradePairs: []*common.TradePair{
@@ -943,8 +961,8 @@ func TestListOrders(t *testing.T) {
                                mockOrders[7],
                                mockOrders[6],
                                mockOrders[2],
-                               mockOrders[3],
                                mockOrders[4],
+                               mockOrders[3],
                                mockOrders[5],
                                mockOrders[0],
                        },
@@ -963,7 +981,6 @@ func TestListOrders(t *testing.T) {
                        },
                        query: mockOrders[3],
                        wantOrders: []*common.Order{
-                               mockOrders[4],
                                mockOrders[5],
                                mockOrders[0],
                        },
@@ -1030,8 +1047,8 @@ func TestAddOrders(t *testing.T) {
                                mockOrders[7],
                                mockOrders[6],
                                mockOrders[2],
-                               mockOrders[3],
                                mockOrders[4],
+                               mockOrders[3],
                                mockOrders[5],
                                mockOrders[0],
                        },
@@ -1055,8 +1072,8 @@ func TestAddOrders(t *testing.T) {
                                mockOrders[7],
                                mockOrders[6],
                                mockOrders[2],
-                               mockOrders[3],
                                mockOrders[4],
+                               mockOrders[3],
                                mockOrders[5],
                                mockOrders[0],
                        },
index 126810d..7e027c0 100644 (file)
@@ -20,9 +20,14 @@ type Engine struct {
        rewardProgram []byte
 }
 
+type orderPosition struct {
+       blockHeight uint64
+       txIndex     uint64
+}
+
 // NewEngine return a new Engine
-func NewEngine(orderBook *OrderBook, feeStrategy FeeStrategy, rewardProgram []byte) *Engine {
-       return &Engine{orderBook: orderBook, feeStrategy: feeStrategy, rewardProgram: rewardProgram}
+func NewEngine(orderBook *OrderBook, rewardProgram []byte) *Engine {
+       return &Engine{orderBook: orderBook, feeStrategy: NewDefaultFeeStrategy(), rewardProgram: rewardProgram}
 }
 
 // HasMatchedTx check does the input trade pair can generate a match deal
@@ -47,7 +52,7 @@ func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, erro
                return nil, errors.New("the specified trade pairs can not be matched")
        }
 
-       tx, err := e.buildMatchTx(sortOrders(e.orderBook.PeekOrders(tradePairs)))
+       tx, partialOrderPositions, err := e.buildMatchTx(sortOrders(e.orderBook.PeekOrders(tradePairs)))
        if err != nil {
                return nil, err
        }
@@ -56,118 +61,102 @@ func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, erro
                e.orderBook.PopOrder(tradePair)
        }
 
-       if err := e.addPartialTradeOrder(tx); err != nil {
+       if err := e.addReOrder(tx, partialOrderPositions); err != nil {
                return nil, err
        }
        return tx, nil
 }
 
-func (e *Engine) addMatchTxFeeOutput(txData *types.TxData, fees []*bc.AssetAmount) error {
+func addMatchTxFeeOutput(txData *types.TxData, fees []*bc.AssetAmount, rewardProgram []byte) {
        for _, feeAmount := range fees {
-               txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, e.rewardProgram))
-       }
-
-       refoundAmount := map[bc.AssetID]uint64{}
-       assetIDs := []bc.AssetID{}
-       refoundScript := [][]byte{}
-       for _, input := range txData.Inputs {
-               refoundAmount[input.AssetID()] += input.Amount()
-               contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
-               if err != nil {
-                       return err
-               }
-
-               assetIDs = append(assetIDs, input.AssetID())
-               refoundScript = append(refoundScript, contractArgs.SellerProgram)
+               txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, rewardProgram))
        }
-
-       for _, output := range txData.Outputs {
-               assetAmount := output.AssetAmount()
-               refoundAmount[*assetAmount.AssetId] -= assetAmount.Amount
-       }
-
-       refoundCount := len(refoundScript)
-       for _, assetID := range assetIDs {
-               amount := refoundAmount[assetID]
-               averageAmount := amount / uint64(refoundCount)
-               if averageAmount == 0 {
-                       averageAmount = 1
-               }
-
-               for i := 0; i < refoundCount && amount > 0; i++ {
-                       if i == refoundCount-1 {
-                               averageAmount = amount
-                       }
-                       txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, averageAmount, refoundScript[i]))
-                       amount -= averageAmount
-               }
-       }
-       return nil
 }
 
-func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
+func (e *Engine) addReOrder(tx *types.Tx, partialOrderPositions []*orderPosition) error {
+       index := 0
        for i, output := range tx.Outputs {
                if !segwit.IsP2WMCScript(output.ControlProgram()) || output.AssetAmount().Amount == 0 {
                        continue
                }
 
-               order, err := common.NewOrderFromOutput(tx, i)
+               partialOrderPos := partialOrderPositions[index]
+               order, err := common.NewOrderFromOutput(tx, i, partialOrderPos.blockHeight, partialOrderPos.txIndex)
                if err != nil {
                        return err
                }
 
+               index++
                e.orderBook.AddOrder(order)
        }
        return nil
 }
 
-func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
+func addRefundOutput(txData *types.TxData, takerProgram []byte) {
+       refundAmount := map[bc.AssetID]uint64{}
+       for _, input := range txData.Inputs {
+               refundAmount[input.AssetID()] += input.Amount()
+       }
+
+       for _, output := range txData.Outputs {
+               assetAmount := output.AssetAmount()
+               refundAmount[*assetAmount.AssetId] -= assetAmount.Amount
+       }
+
+       for assetID, amount := range refundAmount {
+               if amount != 0 {
+                       txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, amount, takerProgram))
+               }
+       }
+}
+
+func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, []*orderPosition, error) {
        txData := &types.TxData{Version: 1}
        for _, order := range orders {
                input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
                txData.Inputs = append(txData.Inputs, input)
        }
 
+       takerPos := takerPos(orders)
        receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
-       allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
-       if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets); err != nil {
-               return nil, err
-       }
+       allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs, takerPos)
 
-       if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Fees); err != nil {
-               return nil, err
+       partialOrderPositions, err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets)
+       if err != nil {
+               return nil, nil, err
        }
 
+       addMatchTxFeeOutput(txData, allocatedAssets.Fees, e.rewardProgram)
+       addRefundOutput(txData, orders[takerPos].SellerProgram)
+
        byteData, err := txData.MarshalText()
        if err != nil {
-               return nil, err
+               return nil, nil, err
        }
 
        txData.SerializedSize = uint64(len(byteData))
-       return types.NewTx(*txData), nil
+       return types.NewTx(*txData), partialOrderPositions, nil
 }
 
-func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts []*bc.AssetAmount, allocatedAssets *AllocatedAssets) error {
+func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts []*bc.AssetAmount, allocatedAssets *AllocatedAssets) ([]*orderPosition, error) {
+       var partialOrderPositions []*orderPosition
        for i, order := range orders {
-               contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
-               if err != nil {
-                       return err
-               }
-
                receivedAmount := receivedAmounts[i].Amount
-               shouldPayAmount := calcShouldPayAmount(receivedAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
+               shouldPayAmount := calcShouldPayAmount(receivedAmount, order.RatioNumerator, order.RatioDenominator)
 
                requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
                exchangeAmount := order.Utxo.Amount - shouldPayAmount
-               isPartialTrade := requestAmount > receivedAmount && CalcRequestAmount(exchangeAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) >= 1
+               isPartialTrade := requestAmount > receivedAmount && CalcRequestAmount(exchangeAmount, order.RatioNumerator, order.RatioDenominator) >= 1
 
                setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmount)
-               txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, allocatedAssets.Receives[i].Amount, contractArgs.SellerProgram))
+
+               txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, allocatedAssets.Receives[i].Amount, order.SellerProgram))
                if isPartialTrade {
                        txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, exchangeAmount, order.Utxo.ControlProgram))
+                       partialOrderPositions = append(partialOrderPositions, &orderPosition{blockHeight: order.BlockHeight, txIndex: order.TxIndex})
                }
        }
-       return nil
+       return partialOrderPositions, nil
 }
 
 func calcOppositeIndex(size int, selfIdx int) int {
@@ -230,6 +219,27 @@ func IsMatched(orders []*common.Order) bool {
        return product.Cmp(one) <= 0
 }
 
+func takerPos(orders []*common.Order) int {
+       for i, order := range orders {
+               if !isMaker(order, orders[calcOppositeIndex(len(orders), i)]) {
+                       return i
+               }
+       }
+       return 0
+}
+
+func isMaker(order, oppositeOrder *common.Order) bool {
+       if order.BlockHeight != oppositeOrder.BlockHeight {
+               return order.BlockHeight < oppositeOrder.BlockHeight
+       }
+
+       if order.TxIndex != oppositeOrder.TxIndex {
+               return order.TxIndex < oppositeOrder.TxIndex
+       }
+
+       return order.UTXOHash().String() < oppositeOrder.UTXOHash().String()
+}
+
 func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
        var arguments [][]byte
        if isPartialTrade {
index 1ac993a..6ff2165 100644 (file)
@@ -89,7 +89,7 @@ func TestGenerateMatchedTxs(t *testing.T) {
 
        for i, c := range cases {
                movStore := mock.NewMovStore([]*common.TradePair{btc2eth, eth2btc}, c.initStoreOrders)
-               matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), NewDefaultFeeStrategy(), mock.RewardProgram)
+               matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), mock.RewardProgram)
                var gotMatchedTxs []*types.Tx
                for matchEngine.HasMatchedTx(c.tradePairs...) {
                        matchedTx, err := matchEngine.NextMatchedTx(c.tradePairs...)
index 1d307a0..3d0de99 100644 (file)
@@ -8,10 +8,12 @@ import (
 )
 
 var (
-       // ErrAmountOfFeeOutOfRange represent The fee charged is out of range
-       ErrAmountOfFeeOutOfRange = errors.New("amount of fee is out of range")
+       // ErrInvalidAmountOfFee represent The fee charged is invalid
+       ErrInvalidAmountOfFee = errors.New("amount of fee is invalid")
 )
 
+const forkBlockHeightAt20201028 = 78968400
+
 // AllocatedAssets represent reallocated assets after calculating fees
 type AllocatedAssets struct {
        Receives []*bc.AssetAmount
@@ -22,12 +24,11 @@ type AllocatedAssets struct {
 type FeeStrategy interface {
        // Allocate will allocate the price differential in matching transaction to the participants and the fee
        // @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
-       // @param priceDiffs price differential of matching transaction
        // @return reallocated assets after calculating fees
-       Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets
+       Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount, takerPos int) *AllocatedAssets
 
        // Validate verify that the fee charged for a matching transaction is correct
-       Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
+       Validate(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64, blockHeight uint64) error
 }
 
 // DefaultFeeStrategy represent the default fee charge strategy
@@ -39,40 +40,84 @@ func NewDefaultFeeStrategy() *DefaultFeeStrategy {
 }
 
 // Allocate will allocate the price differential in matching transaction to the participants and the fee
-func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets {
+func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount, takerPos int) *AllocatedAssets {
        receives := make([]*bc.AssetAmount, len(receiveAmounts))
        fees := make([]*bc.AssetAmount, len(receiveAmounts))
 
        for i, receiveAmount := range receiveAmounts {
-               standFee := d.calcMinFeeAmount(receiveAmount.Amount)
-               fee := standFee + priceDiffs[i].Amount
-               if maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount); fee > maxFeeAmount {
-                       fee = maxFeeAmount
-               }
-
-               receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: receiveAmount.Amount - standFee}
+               fee := calcMinFeeAmount(receiveAmount.Amount)
+               receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: receiveAmount.Amount - fee}
                fees[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: fee}
+
+               if i == takerPos {
+                       for _, priceDiff := range priceDiffs {
+                               if *priceDiff.AssetId == *receiveAmount.AssetId {
+                                       fee = calcMinFeeAmount(priceDiff.Amount)
+                                       priceDiff.Amount -= fee
+                                       fees[i].Amount += fee
+                               }
+                       }
+               }
        }
        return &AllocatedAssets{Receives: receives, Fees: fees}
 }
 
 // Validate verify that the fee charged for a matching transaction is correct
-func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
+func (d *DefaultFeeStrategy) Validate(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64, blockHeight uint64) error {
+       if blockHeight < forkBlockHeightAt20201028 {
+               return legendValidateFee(receiveAmounts, feeAmounts)
+       }
+       return validateFee(receiveAmounts, priceDiffs, feeAmounts)
+}
+
+func validateFee(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
+       existTaker := false
+       for _, receiveAmount := range receiveAmounts {
+               feeAmount := calcMinFeeAmount(receiveAmount.Amount)
+               realFeeAmount := feeAmounts[*receiveAmount.AssetId]
+               if equalsFeeAmount(realFeeAmount, feeAmount) {
+                       continue
+               }
+
+               if existTaker {
+                       return ErrInvalidAmountOfFee
+               }
+
+               for _, priceDiff := range priceDiffs {
+                       if *priceDiff.AssetId == *receiveAmount.AssetId {
+                               feeAmount += calcMinFeeAmount(priceDiff.Amount)
+                       }
+               }
+
+               if !equalsFeeAmount(realFeeAmount, feeAmount) {
+                       return ErrInvalidAmountOfFee
+               }
+               existTaker = true
+       }
+       return nil
+}
+
+func equalsFeeAmount(realFeeAmount, feeAmount uint64) bool {
+       var tolerance float64 = 5
+       return math.Abs(float64(realFeeAmount)-float64(feeAmount)) < tolerance
+}
+
+func legendValidateFee(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
        for _, receiveAmount := range receiveAmounts {
-               feeAmount := feeAmounts[*receiveAmount.AssetId]
-               maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount)
-               minFeeAmount := d.calcMinFeeAmount(receiveAmount.Amount)
-               if feeAmount < minFeeAmount || feeAmount > maxFeeAmount {
-                       return ErrAmountOfFeeOutOfRange
+               realFeeAmount := feeAmounts[*receiveAmount.AssetId]
+               minFeeAmount := calcMinFeeAmount(receiveAmount.Amount)
+               maxFeeAmount := calcMaxFeeAmount(receiveAmount.Amount)
+               if realFeeAmount < minFeeAmount || realFeeAmount > maxFeeAmount {
+                       return ErrInvalidAmountOfFee
                }
        }
        return nil
 }
 
-func (d *DefaultFeeStrategy) calcMinFeeAmount(amount uint64) uint64 {
+func calcMinFeeAmount(amount uint64) uint64 {
        return uint64(math.Ceil(float64(amount) / 1000))
 }
 
-func (d *DefaultFeeStrategy) calcMaxFeeAmount(amount uint64) uint64 {
+func calcMaxFeeAmount(amount uint64) uint64 {
        return uint64(math.Ceil(float64(amount) * 0.05))
 }
index 0b5da6c..12daba6 100644 (file)
@@ -28,6 +28,9 @@ var (
                                Amount:         10,
                                ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 50, 1),
                        },
+                       BlockHeight:   100,
+                       TxIndex:       1,
+                       SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"),
                },
                {
                        FromAssetID:      &BTC,
@@ -40,6 +43,9 @@ var (
                                Amount:         20,
                                ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
                        },
+                       BlockHeight:   100,
+                       TxIndex:       2,
+                       SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"),
                },
                {
                        FromAssetID:      &BTC,
@@ -52,6 +58,7 @@ var (
                                Amount:         15,
                                ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
                        },
+                       SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"),
                },
                {
                        FromAssetID:      &BTC,
@@ -64,6 +71,7 @@ var (
                                Amount:         17,
                                ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
                        },
+                       SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"),
                },
        }
 
@@ -79,6 +87,7 @@ var (
                                Amount:         510,
                                ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253"), 1, 51.0),
                        },
+                       SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253"),
                },
                {
                        FromAssetID:      &ETH,
@@ -91,6 +100,9 @@ var (
                                Amount:         416,
                                ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254"), 1, 52.0),
                        },
+                       BlockHeight:   100,
+                       TxIndex:       2,
+                       SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254"),
                },
                {
                        FromAssetID:      &ETH,
@@ -103,6 +115,9 @@ var (
                                Amount:         810,
                                ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 54.0),
                        },
+                       BlockHeight:   101,
+                       TxIndex:       1,
+                       SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"),
                },
                {
                        FromAssetID:      &ETH,
@@ -115,6 +130,9 @@ var (
                                Amount:         600,
                                ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256"), 1, 150.0),
                        },
+                       BlockHeight:   101,
+                       TxIndex:       3,
+                       SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256"),
                },
        }
 
@@ -130,6 +148,7 @@ var (
                                Amount:         100,
                                ControlProgram: MustCreateP2WMCProgram(ETC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 2.0),
                        },
+                       SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"),
                },
        }
 
@@ -145,6 +164,7 @@ var (
                                Amount:         50,
                                ControlProgram: MustCreateP2WMCProgram(EOS, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3"), 2, 1.0),
                        },
+                       SellerProgram: testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3"),
                },
        }
 
@@ -160,6 +180,7 @@ var (
                                Amount:         500,
                                ControlProgram: MustCreateP2WMCProgram(EOS, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86"), 2, 1.0),
                        },
+                       SellerProgram: testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86"),
                },
        }
 
@@ -175,6 +196,7 @@ var (
                                Amount:         1000,
                                ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc"), 1, 100.0),
                        },
+                       SellerProgram: testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc"),
                },
        }
 
@@ -261,7 +283,9 @@ var (
                // partial matched transaction from Btc2EthOrders[0], Eth2BtcOrders[1]
                types.NewTx(types.TxData{
                        Inputs: []*types.TxInput{
+                               // maker
                                types.NewSpendInput([][]byte{vm.Int64Bytes(416), vm.Int64Bytes(0), vm.Int64Bytes(0)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               // taker
                                types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eth2BtcOrders[1].Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.SourcePos, Eth2BtcOrders[1].Utxo.ControlProgram),
                        },
                        Outputs: []*types.TxOutput{
@@ -285,15 +309,19 @@ var (
                                types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
                                types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
                                // fee
-                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 11, RewardProgram),
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 2, RewardProgram),
                                types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
+                               // refund to taker
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
                        },
                }),
 
                // partial matched transaction from Btc2EthOrders[0], Eth2BtcOrders[2]
                types.NewTx(types.TxData{
                        Inputs: []*types.TxInput{
+                               // maker
                                types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               // taker
                                types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *Eth2BtcOrders[2].Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.SourcePos, Eth2BtcOrders[2].Utxo.ControlProgram),
                        },
                        Outputs: []*types.TxOutput{
@@ -302,16 +330,17 @@ var (
                                // re-order
                                types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram),
                                // fee
-                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 25, RewardProgram),
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
                                types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 1, RewardProgram),
-                               // refund
-                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
-                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+                               // re-order
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 40, Eth2BtcOrders[2].SellerProgram),
                        },
                }),
                types.NewTx(types.TxData{
                        Inputs: []*types.TxInput{
+                               // maker
                                types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(0)}, *Btc2EthOrders[1].Utxo.SourceID, *Btc2EthOrders[1].FromAssetID, Btc2EthOrders[1].Utxo.Amount, Btc2EthOrders[1].Utxo.SourcePos, Btc2EthOrders[1].Utxo.ControlProgram),
+                               // taker
                                types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, testutil.MustDecodeHash("39bdb7058a0c31fb740af8e3c382bf608efff1b041cd4dd461332722ad24552a"), *Eth2BtcOrders[2].FromAssetID, 270, 2, Eth2BtcOrders[2].Utxo.ControlProgram),
                        },
                        Outputs: []*types.TxOutput{
@@ -365,9 +394,9 @@ var (
                                types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eos2BtcOrders[0].Utxo.SourceID, *Eos2BtcOrders[0].FromAssetID, Eos2BtcOrders[0].Utxo.Amount, Eos2BtcOrders[0].Utxo.SourcePos, Eos2BtcOrders[0].Utxo.ControlProgram),
                        },
                        Outputs: []*types.TxOutput{
-                               types.NewIntraChainOutput(ETH, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
-                               types.NewIntraChainOutput(EOS, 999, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86")),
-                               types.NewIntraChainOutput(BTC, 9, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc")),
+                               types.NewIntraChainOutput(ETH, 499, Btc2EthOrders[0].SellerProgram),
+                               types.NewIntraChainOutput(EOS, 999, Eth2EosOrders[0].SellerProgram),
+                               types.NewIntraChainOutput(BTC, 9, Eos2BtcOrders[0].SellerProgram),
                                // fee
                                types.NewIntraChainOutput(ETH, 1, RewardProgram),
                                types.NewIntraChainOutput(EOS, 1, RewardProgram),
@@ -443,42 +472,46 @@ var (
                // full matched transaction from Btc2EthOrders[0] Eth2BtcOrders[3]
                types.NewTx(types.TxData{
                        Inputs: []*types.TxInput{
+                               // maker
                                types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               // taker
                                types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Eth2BtcOrders[3].Utxo.SourceID, *Eth2BtcOrders[3].FromAssetID, Eth2BtcOrders[3].Utxo.Amount, Eth2BtcOrders[3].Utxo.SourcePos, Eth2BtcOrders[3].Utxo.ControlProgram),
                        },
                        Outputs: []*types.TxOutput{
                                types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
                                types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
                                // fee
-                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 25, RewardProgram),
-                               types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 1, RewardProgram),
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 2, RewardProgram),
                                // refund
-                               types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
-                               types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
-                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
-                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 5, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 100, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
                        },
                }),
        }
 )
 
 func MustCreateP2WMCProgram(requestAsset bc.AssetID, sellerProgram []byte, ratioNumerator, ratioDenominator int64) []byte {
-       contractArgs := vmutil.MagneticContractArgs{
+       contractArgs := createContractArgs(requestAsset, sellerProgram, ratioNumerator, ratioDenominator)
+       program, err := vmutil.P2WMCProgram(*contractArgs)
+       if err != nil {
+               panic(err)
+       }
+       return program
+}
+
+func createContractArgs(requestAsset bc.AssetID, sellerProgram []byte, ratioNumerator, ratioDenominator int64) *vmutil.MagneticContractArgs {
+       return &vmutil.MagneticContractArgs{
                RequestedAsset:   requestAsset,
                RatioNumerator:   ratioNumerator,
                RatioDenominator: ratioDenominator,
                SellerProgram:    sellerProgram,
                SellerKey:        testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
        }
-       program, err := vmutil.P2WMCProgram(contractArgs)
-       if err != nil {
-               panic(err)
-       }
-       return program
 }
 
-func MustNewOrderFromOutput(tx *types.Tx, outputIndex int) *common.Order {
-       order, err := common.NewOrderFromOutput(tx, outputIndex)
+func MustNewOrderFromOutputV2(tx *types.Tx, outputIndex int, blockHeight, txIndex uint64) *common.Order {
+       order, err := common.NewOrderFromOutput(tx, outputIndex, blockHeight, txIndex)
        if err != nil {
                panic(err)
        }
@@ -486,6 +519,10 @@ func MustNewOrderFromOutput(tx *types.Tx, outputIndex int) *common.Order {
        return order
 }
 
+func MustNewOrderFromOutput(tx *types.Tx, outputIndex int) *common.Order {
+       return MustNewOrderFromOutputV2(tx, outputIndex, 0, 0)
+}
+
 func hashPtr(hash bc.Hash) *bc.Hash {
        return &hash
 }
index 4a08cf5..7dca133 100644 (file)
@@ -62,11 +62,11 @@ func (m *Core) ApplyBlock(block *types.Block) error {
                return m.InitChainStatus(&blockHash)
        }
 
-       if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
+       if err := m.validateMatchedTxSequence(movTxs(block)); err != nil {
                return err
        }
 
-       addOrders, deleteOrders, err := decodeTxsOrders(block.Transactions)
+       addOrders, deleteOrders, err := decodeTxsOrders(movTxs(block))
        if err != nil {
                return err
        }
@@ -74,24 +74,36 @@ func (m *Core) ApplyBlock(block *types.Block) error {
        return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
 }
 
+// Tx contains raw transaction and the sequence of tx in block
+type Tx struct {
+       rawTx       *types.Tx
+       blockHeight uint64
+       txIndex     uint64
+}
+
+// NewTx create a new Tx instance
+func NewTx(tx *types.Tx, blockHeight, txIndex uint64) *Tx {
+       return &Tx{rawTx: tx, blockHeight: blockHeight, txIndex: txIndex}
+}
+
 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
-func (m *Core) BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
-       if blockHeight <= m.startBlockHeight {
+func (m *Core) BeforeProposalBlock(block *types.Block, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
+       if block.Height <= m.startBlockHeight {
                return nil, nil
        }
 
-       orderBook, err := buildOrderBook(m.movStore, txs)
+       orderBook, err := buildOrderBook(m.movStore, movTxs(block))
        if err != nil {
                return nil, err
        }
 
-       program, _ := getRewardProgram(blockHeight)
+       program, _ := getRewardProgram(block.Height)
        rewardProgram, err := hex.DecodeString(program)
        if err != nil {
                return nil, errNotConfiguredRewardProgram
        }
 
-       matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(), rewardProgram)
+       matchEngine := match.NewEngine(orderBook, rewardProgram)
        tradePairIterator := database.NewTradePairIterator(m.movStore)
        matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
        return matchCollector.result()
@@ -123,7 +135,7 @@ func (m *Core) DetachBlock(block *types.Block) error {
                return nil
        }
 
-       deleteOrders, addOrders, err := decodeTxsOrders(block.Transactions)
+       deleteOrders, addOrders, err := decodeTxsOrders(movTxs(block))
        if err != nil {
                return err
        }
@@ -242,13 +254,9 @@ func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
 
 func validateCancelOrderTx(tx *types.Tx) error {
        for _, input := range tx.Inputs {
-               if !segwit.IsP2WMCScript(input.ControlProgram()) {
+               if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
                        return errInputProgramMustP2WMCScript
                }
-
-               if contract.IsTradeClauseSelector(input) {
-                       return errExistTradeInCancelOrderTx
-               }
        }
        return nil
 }
@@ -294,7 +302,7 @@ func validateMatchedTx(tx *types.Tx, blockHeight uint64) error {
                toAssetIDMap[order.ToAssetID.String()] = true
        }
 
-       if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
+       if inputSize := len(tx.Inputs); len(fromAssetIDMap) != inputSize || len(toAssetIDMap) != inputSize {
                return errAssetIDMustUniqueInMatchedTx
        }
 
@@ -313,38 +321,38 @@ func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
                }
        }
 
-       orders, err := getDeleteOrdersFromTx(tx)
+       orders, err := parseDeleteOrdersFromTx(tx)
        if err != nil {
                return err
        }
 
-       receivedAmount, _ := match.CalcReceivedAmount(orders)
+       receivedAmount, priceDiffs := match.CalcReceivedAmount(orders)
        feeAmounts := make(map[bc.AssetID]uint64)
        for assetID, fee := range matchedTxFees {
                feeAmounts[assetID] = fee.amount
        }
 
        feeStrategy := match.NewDefaultFeeStrategy()
-       return feeStrategy.Validate(receivedAmount, feeAmounts)
+       return feeStrategy.Validate(receivedAmount, priceDiffs, feeAmounts, blockHeight)
 }
 
-func (m *Core) validateMatchedTxSequence(txs []*types.Tx) error {
+func (m *Core) validateMatchedTxSequence(txs []*Tx) error {
        orderBook := match.NewOrderBook(m.movStore, nil, nil)
        for _, tx := range txs {
-               if common.IsMatchedTx(tx) {
-                       tradePairs, err := getTradePairsFromMatchedTx(tx)
+               if common.IsMatchedTx(tx.rawTx) {
+                       tradePairs, err := parseTradePairsFromMatchedTx(tx.rawTx)
                        if err != nil {
                                return err
                        }
 
                        orders := orderBook.PeekOrders(tradePairs)
-                       if err := validateSpendOrders(tx, orders); err != nil {
+                       if err := validateSpendOrders(tx.rawTx, orders); err != nil {
                                return err
                        }
 
                        orderBook.PopOrders(tradePairs)
-               } else if common.IsCancelOrderTx(tx) {
-                       orders, err := getDeleteOrdersFromTx(tx)
+               } else if common.IsCancelOrderTx(tx.rawTx) {
+                       orders, err := parseDeleteOrdersFromTx(tx.rawTx)
                        if err != nil {
                                return err
                        }
@@ -354,7 +362,7 @@ func (m *Core) validateMatchedTxSequence(txs []*types.Tx) error {
                        }
                }
 
-               addOrders, err := getAddOrdersFromTx(tx)
+               addOrders, err := parseAddOrdersFromTx(tx)
                if err != nil {
                        return err
                }
@@ -390,11 +398,11 @@ func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
        return nil
 }
 
-func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
+func decodeTxsOrders(txs []*Tx) ([]*common.Order, []*common.Order, error) {
        deleteOrderMap := make(map[string]*common.Order)
        addOrderMap := make(map[string]*common.Order)
        for _, tx := range txs {
-               addOrders, err := getAddOrdersFromTx(tx)
+               addOrders, err := parseAddOrdersFromTx(tx)
                if err != nil {
                        return nil, nil, err
                }
@@ -403,7 +411,7 @@ func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error)
                        addOrderMap[order.Key()] = order
                }
 
-               deleteOrders, err := getDeleteOrdersFromTx(tx)
+               deleteOrders, err := parseDeleteOrdersFromTx(tx.rawTx)
                if err != nil {
                        return nil, nil, err
                }
@@ -417,15 +425,15 @@ func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error)
        return addOrders, deleteOrders, nil
 }
 
-func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
+func buildOrderBook(store database.MovStore, txs []*Tx) (*match.OrderBook, error) {
        var arrivalAddOrders, arrivalDelOrders []*common.Order
        for _, tx := range txs {
-               addOrders, err := getAddOrdersFromTx(tx)
+               addOrders, err := parseAddOrdersFromTx(tx)
                if err != nil {
                        return nil, err
                }
 
-               delOrders, err := getDeleteOrdersFromTx(tx)
+               delOrders, err := parseDeleteOrdersFromTx(tx.rawTx)
                if err != nil {
                        return nil, err
                }
@@ -437,9 +445,9 @@ func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook,
        return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
 }
 
-func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
+func parseAddOrdersFromTx(tx *Tx) ([]*common.Order, error) {
        var orders []*common.Order
-       for i, output := range tx.Outputs {
+       for i, output := range tx.rawTx.Outputs {
                if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
                        continue
                }
@@ -448,7 +456,7 @@ func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
                        continue
                }
 
-               order, err := common.NewOrderFromOutput(tx, i)
+               order, err := common.NewOrderFromOutput(tx.rawTx, i, tx.blockHeight, tx.txIndex)
                if err != nil {
                        return nil, err
                }
@@ -458,7 +466,7 @@ func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
        return orders, nil
 }
 
-func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
+func parseDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
        var orders []*common.Order
        for i, input := range tx.Inputs {
                if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
@@ -475,7 +483,7 @@ func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
        return orders, nil
 }
 
-func getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
+func parseTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
        var tradePairs []*common.TradePair
        for _, tx := range tx.Inputs {
                contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
@@ -504,6 +512,14 @@ func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*commo
        return addOrders, deleteOrders
 }
 
+func movTxs(block *types.Block) []*Tx {
+       var movTxs []*Tx
+       for i, tx := range block.Transactions {
+               movTxs = append(movTxs, NewTx(tx, block.Height, uint64(i)))
+       }
+       return movTxs
+}
+
 // getRewardProgram return the reward program by specified block height
 // if no reward program configured, then will return empty string
 // if reward program of 0-100 height is configured, but the specified height is 200, then will return  0-100's reward program
index 9b4e037..7df7c2a 100644 (file)
@@ -39,7 +39,7 @@ func TestApplyBlock(t *testing.T) {
                                },
                        },
                        blockFunc:   applyBlock,
-                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0)},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutputV2(mock.Btc2EthMakerTxs[0], 0, 2, 0), mock.MustNewOrderFromOutputV2(mock.Eth2BtcMakerTxs[0], 0, 2, 1)},
                        wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
                },
                {
@@ -55,10 +55,10 @@ func TestApplyBlock(t *testing.T) {
                        },
                        blockFunc: applyBlock,
                        wantOrders: []*common.Order{
-                               mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
-                               mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
-                               mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
-                               mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+                               mock.MustNewOrderFromOutputV2(mock.Btc2EthMakerTxs[0], 0, 2, 0),
+                               mock.MustNewOrderFromOutputV2(mock.Eth2BtcMakerTxs[0], 0, 2, 1),
+                               mock.MustNewOrderFromOutputV2(mock.Eos2EtcMakerTxs[0], 0, 2, 2),
+                               mock.MustNewOrderFromOutputV2(mock.Eth2EosMakerTxs[0], 0, 2, 3),
                        },
                        wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
                },
@@ -85,7 +85,7 @@ func TestApplyBlock(t *testing.T) {
                        },
                        blockFunc:   applyBlock,
                        initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
-                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[0], 1, 2, 0)},
                        wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
                },
                {
@@ -98,7 +98,7 @@ func TestApplyBlock(t *testing.T) {
                        },
                        blockFunc:   applyBlock,
                        initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
-                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[3], 1, 2, 1)},
                        wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
                },
                {
@@ -113,7 +113,7 @@ func TestApplyBlock(t *testing.T) {
                        },
                        blockFunc:   applyBlock,
                        initOrders:  []*common.Order{},
-                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1)},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[4], 1, 2, 2)},
                        wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
                },
                {
@@ -133,8 +133,8 @@ func TestApplyBlock(t *testing.T) {
                        blockFunc:  applyBlock,
                        initOrders: []*common.Order{},
                        wantOrders: []*common.Order{
-                               mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
-                               mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+                               mock.MustNewOrderFromOutputV2(mock.MatchedTxs[4], 1, 2, 3),
+                               mock.MustNewOrderFromOutputV2(mock.Eth2EosMakerTxs[0], 0, 2, 4),
                        },
                        wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
                },
@@ -152,7 +152,7 @@ func TestApplyBlock(t *testing.T) {
                        },
                        blockFunc:   applyBlock,
                        initOrders:  []*common.Order{},
-                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[7], 2)},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[7], 2, 2, 4)},
                        wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
                },
                {
@@ -197,9 +197,13 @@ func TestApplyBlock(t *testing.T) {
                                        mock.MatchedTxs[1],
                                },
                        },
-                       blockFunc:   detachBlock,
-                       initOrders:  []*common.Order{mock.Btc2EthOrders[1]},
-                       wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
+                       blockFunc:  detachBlock,
+                       initOrders: []*common.Order{mock.Btc2EthOrders[1]},
+                       wantOrders: []*common.Order{
+                               orderWithHeightAndTxIndex(mock.Btc2EthOrders[0], 0, 0),
+                               mock.Btc2EthOrders[1],
+                               orderWithHeightAndTxIndex(mock.Eth2BtcOrders[0], 0, 0),
+                       },
                        wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
                },
                {
@@ -210,9 +214,12 @@ func TestApplyBlock(t *testing.T) {
                                        mock.MatchedTxs[0],
                                },
                        },
-                       blockFunc:   detachBlock,
-                       initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
-                       wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
+                       blockFunc:  detachBlock,
+                       initOrders: []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[0], 1, 1, 0)},
+                       wantOrders: []*common.Order{
+                               orderWithHeightAndTxIndex(mock.Btc2EthOrders[0], 0, 0),
+                               orderWithHeightAndTxIndex(mock.Eth2BtcOrders[1], 0, 0),
+                       },
                        wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
                },
                {
@@ -223,9 +230,13 @@ func TestApplyBlock(t *testing.T) {
                                        mock.MatchedTxs[2], mock.MatchedTxs[3],
                                },
                        },
-                       blockFunc:   detachBlock,
-                       initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
-                       wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+                       blockFunc:  detachBlock,
+                       initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
+                       wantOrders: []*common.Order{
+                               orderWithHeightAndTxIndex(mock.Btc2EthOrders[0], 0, 0),
+                               orderWithHeightAndTxIndex(mock.Btc2EthOrders[1], 0, 0),
+                               orderWithHeightAndTxIndex(mock.Eth2BtcOrders[2], 0, 0),
+                       },
                        wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
                },
                {
@@ -266,12 +277,12 @@ func TestApplyBlock(t *testing.T) {
 
                movCore := &Core{movStore: store}
                if err := c.blockFunc(movCore, c.block); err != c.wantError {
-                       t.Errorf("#%d(%s):apply block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
+                       t.Errorf("#%d(%s):want error(%v), got error(%v)", i, c.desc, c.wantError, err)
                }
 
                gotOrders := queryAllOrders(store)
                if !ordersEquals(c.wantOrders, gotOrders) {
-                       t.Errorf("#%d(%s):apply block want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
+                       t.Errorf("#%d(%s):want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
                }
 
                dbState, err := store.GetMovDatabaseState()
@@ -280,7 +291,7 @@ func TestApplyBlock(t *testing.T) {
                }
 
                if !testutil.DeepEqual(c.wantDBState, dbState) {
-                       t.Errorf("#%d(%s):apply block want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
+                       t.Errorf("#%d(%s):want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
                }
 
                testDB.Close()
@@ -471,7 +482,7 @@ func TestValidateBlock(t *testing.T) {
                                },
                        },
                        verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
-                       wantError:     match.ErrAmountOfFeeOutOfRange,
+                       wantError:     match.ErrInvalidAmountOfFee,
                },
                {
                        desc: "ratio numerator is zero",
@@ -516,7 +527,7 @@ func TestValidateBlock(t *testing.T) {
 
        for i, c := range cases {
                movCore := &Core{}
-               c.block.Height = 3456786543
+               c.block.Height = 84000000
                if err := movCore.ValidateBlock(c.block, c.verifyResults); err != c.wantError {
                        t.Errorf("#%d(%s):validate block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
                }
@@ -527,33 +538,29 @@ func TestCalcMatchedTxFee(t *testing.T) {
        cases := []struct {
                desc             string
                tx               types.TxData
-               maxFeeRate       float64
                wantMatchedTxFee map[bc.AssetID]*matchedTxFee
        }{
                {
-                       desc:       "fee less than max fee",
-                       maxFeeRate: 0.05,
+                       desc: "fee less than max fee",
                        wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
-                               mock.ETH: {amount: 11, rewardProgram: mock.RewardProgram},
                                mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+                               mock.ETH: {amount: 2, rewardProgram: mock.RewardProgram},
                        },
                        tx: mock.MatchedTxs[1].TxData,
                },
                {
-                       desc:       "fee refund in tx",
-                       maxFeeRate: 0.05,
+                       desc: "fee refund in tx",
                        wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
-                               mock.ETH: {amount: 25, rewardProgram: mock.RewardProgram},
                                mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+                               mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
                        },
                        tx: mock.MatchedTxs[2].TxData,
                },
                {
-                       desc:       "no price diff",
-                       maxFeeRate: 0.05,
+                       desc: "no price diff",
                        wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
-                               mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
                                mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+                               mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
                        },
                        tx: mock.MatchedTxs[0].TxData,
                },
@@ -644,7 +651,7 @@ func TestBeforeProposalBlock(t *testing.T) {
                }
 
                movCore := &Core{movStore: store}
-               gotMatchedTxs, err := movCore.BeforeProposalBlock(nil, 2, c.gasLeft, func() bool { return false })
+               gotMatchedTxs, err := movCore.BeforeProposalBlock(&types.Block{BlockHeader: types.BlockHeader{Height: 2}}, c.gasLeft, func() bool { return false })
                if err != nil {
                        t.Fatal(err)
                }
@@ -672,43 +679,43 @@ func TestValidateMatchedTxSequence(t *testing.T) {
        cases := []struct {
                desc         string
                initOrders   []*common.Order
-               transactions []*types.Tx
+               transactions []*Tx
                wantError    error
        }{
                {
                        desc:         "both db orders and transactions is empty",
                        initOrders:   []*common.Order{},
-                       transactions: []*types.Tx{},
+                       transactions: []*Tx{},
                        wantError:    nil,
                },
                {
                        desc:         "existing matched orders in db, and transactions is empty",
                        initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
-                       transactions: []*types.Tx{},
+                       transactions: []*Tx{},
                        wantError:    nil,
                },
                {
                        desc:         "db orders is empty, but transactions has matched tx",
                        initOrders:   []*common.Order{},
-                       transactions: []*types.Tx{mock.MatchedTxs[1]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}},
                        wantError:    errNotMatchedOrder,
                },
                {
                        desc:         "existing matched orders in db, and corresponding matched tx in transactions",
                        initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
-                       transactions: []*types.Tx{mock.MatchedTxs[1]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}},
                        wantError:    nil,
                },
                {
                        desc:         "package matched tx, one order from db, and the another order from transactions",
                        initOrders:   []*common.Order{mock.Btc2EthOrders[0]},
-                       transactions: []*types.Tx{mock.Eth2BtcMakerTxs[0], mock.MatchedTxs[10]},
+                       transactions: []*Tx{{rawTx: mock.Eth2BtcMakerTxs[0]}, {rawTx: mock.MatchedTxs[10]}},
                        wantError:    nil,
                },
                {
                        desc:         "two matched txs use the same orders",
                        initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
-                       transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[1]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.MatchedTxs[1]}},
                        wantError:    errNotMatchedOrder,
                },
                {
@@ -717,7 +724,7 @@ func TestValidateMatchedTxSequence(t *testing.T) {
                                mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
                                mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
                        },
-                       transactions: []*types.Tx{mock.MatchedTxs[8]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[8]}},
                        wantError:    nil,
                },
                {
@@ -726,31 +733,31 @@ func TestValidateMatchedTxSequence(t *testing.T) {
                                mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
                                mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
                        },
-                       transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[8]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.MatchedTxs[8]}},
                        wantError:    errSpendOutputIDIsIncorrect,
                },
                {
                        desc:         "matched tx and orders from packaged transactions",
                        initOrders:   []*common.Order{},
-                       transactions: []*types.Tx{mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1], mock.MatchedTxs[4]},
+                       transactions: []*Tx{{rawTx: mock.Btc2EthMakerTxs[0]}, {rawTx: mock.Eth2BtcMakerTxs[1]}, {rawTx: mock.MatchedTxs[4]}},
                        wantError:    nil,
                },
                {
                        desc:         "package the matched tx first, then package match orders",
                        initOrders:   []*common.Order{},
-                       transactions: []*types.Tx{mock.MatchedTxs[4], mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[4]}, {rawTx: mock.Btc2EthMakerTxs[0]}, {rawTx: mock.Eth2BtcMakerTxs[1]}},
                        wantError:    errNotMatchedOrder,
                },
                {
                        desc:         "cancel order in transactions",
                        initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
-                       transactions: []*types.Tx{mock.Btc2EthCancelTxs[0], mock.MatchedTxs[1]},
+                       transactions: []*Tx{{rawTx: mock.Btc2EthCancelTxs[0]}, {rawTx: mock.MatchedTxs[1]}},
                        wantError:    errNotMatchedOrder,
                },
                {
                        desc:         "package cancel order after match tx",
                        initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
-                       transactions: []*types.Tx{mock.MatchedTxs[1], mock.Btc2EthCancelTxs[0]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.Btc2EthCancelTxs[0]}},
                        wantError:    nil,
                },
                {
@@ -759,7 +766,7 @@ func TestValidateMatchedTxSequence(t *testing.T) {
                                mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
                                mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
                        },
-                       transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[9]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.MatchedTxs[9]}},
                        wantError:    nil,
                },
                {
@@ -768,19 +775,19 @@ func TestValidateMatchedTxSequence(t *testing.T) {
                                mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
                                mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
                        },
-                       transactions: []*types.Tx{mock.MatchedTxs[9], mock.MatchedTxs[1]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[9]}, {rawTx: mock.MatchedTxs[1]}},
                        wantError:    nil,
                },
                {
                        desc:         "package partial matched tx from db orders, and the re-pending order continue to match",
                        initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
-                       transactions: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[2]}, {rawTx: mock.MatchedTxs[3]}},
                        wantError:    nil,
                },
                {
                        desc:         "cancel the re-pending order",
                        initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
-                       transactions: []*types.Tx{mock.MatchedTxs[2], mock.Btc2EthCancelTxs[1], mock.MatchedTxs[3]},
+                       transactions: []*Tx{{rawTx: mock.MatchedTxs[2]}, {rawTx: mock.Btc2EthCancelTxs[1]}, {rawTx: mock.MatchedTxs[3]}},
                        wantError:    errNotMatchedOrder,
                },
        }
@@ -844,3 +851,9 @@ func ordersEquals(orders1 []*common.Order, orders2 []*common.Order) bool {
 func hashPtr(hash bc.Hash) *bc.Hash {
        return &hash
 }
+
+func orderWithHeightAndTxIndex(order *common.Order, blockHeight, txIndex uint64) *common.Order {
+       order.BlockHeight = blockHeight
+       order.TxIndex = txIndex
+       return order
+}
index c41d7dc..69158f1 100644 (file)
@@ -295,7 +295,11 @@ func (c *crossInAction) Build(ctx context.Context, builder *TemplateBuilder) err
        // arguments will be set when materializeWitnesses
        txin := types.NewCrossChainInput(nil, c.SourceID, *c.AssetId, c.Amount, c.SourcePos, c.VMVersion, c.RawDefinitionByte, c.IssuanceProgram)
        tplIn := &SigningInstruction{}
+       blockHeight := ctx.Value("block_height").(uint64)
        fed := cfg.CommonConfig.Federation
+       if blockHeight > consensus.CrossInForkHeight {
+               fed = cfg.SingleFederationConfig()
+       }
 
        if !common.IsOpenFederationIssueAsset(c.RawDefinitionByte) {
                tplIn.AddRawWitnessKeys(fed.Xpubs, cfg.FedAddressPath, fed.Quorum)
index af75e32..4dd642d 100644 (file)
@@ -1,6 +1,7 @@
 package commands
 
 import (
+       "fmt"
        "strings"
        "time"
 
@@ -8,6 +9,7 @@ import (
        "github.com/spf13/cobra"
 
        "github.com/bytom/vapor/node"
+       "github.com/bytom/vapor/toolbar/osssync/download"
 )
 
 const logModule = "cmd"
@@ -55,6 +57,9 @@ func init() {
        runNodeCmd.Flags().Int("ws.max_num_websockets", config.Websocket.MaxNumWebsockets, "Max number of websocket connections")
        runNodeCmd.Flags().Int("ws.max_num_concurrent_reqs", config.Websocket.MaxNumConcurrentReqs, "Max number of concurrent websocket requests that may be processed concurrently")
 
+       // OSS
+       runNodeCmd.Flags().String("oss.url", config.Oss.Url, "URL of OSS")
+
        RootCmd.AddCommand(runNodeCmd)
 }
 
@@ -81,6 +86,13 @@ func runNode(cmd *cobra.Command, args []string) error {
 
        // Create & start node
        n := node.NewNode(config)
+
+       // Get blocks from OSS
+       if err := download.Run(n, config.Oss.Url); err != nil {
+               fmt.Println("Failed to get blocks from oss: ", err)
+       }
+
+       // Start node
        if _, err := n.Start(); err != nil {
                log.WithFields(log.Fields{"module": logModule, "err": err}).Fatal("failed to start node")
        }
index e9490f9..a4c8ca5 100644 (file)
@@ -29,6 +29,7 @@ type Config struct {
        Websocket  *WebsocketConfig  `mapstructure:"ws"`
        Federation *FederationConfig `mapstructure:"federation"`
        CrossChain *CrossChainConfig `mapstructure:"cross_chain"`
+       Oss        *OssConfig        `mapstructure:"oss"`
 }
 
 // Default configurable parameters.
@@ -42,6 +43,7 @@ func DefaultConfig() *Config {
                Websocket:  DefaultWebsocketConfig(),
                Federation: DefaultFederationConfig(),
                CrossChain: DefaultCrossChainConfig(),
+               Oss:        DefaultOssConfig(),
        }
 }
 
@@ -220,6 +222,10 @@ type CrossChainConfig struct {
        AssetWhitelist string `mapstructure:"asset_whitelist"`
 }
 
+type OssConfig struct {
+       Url string `mapstructure:"url"`
+}
+
 // Default configurable rpc's auth parameters.
 func DefaultRPCAuthConfig() *RPCAuthConfig {
        return &RPCAuthConfig{
@@ -265,6 +271,16 @@ func DefaultFederationConfig() *FederationConfig {
        }
 }
 
+// Default configurable federation parameters.
+func SingleFederationConfig() *FederationConfig {
+       return &FederationConfig{
+               Xpubs: []chainkd.XPub{
+                       xpub("0f810c52bcd0d44482d63ef6131712b32e08b2a20c5daf134072b870a789623a726eb61e4021caa7158fbc6ca99827485c7f60010913a12a810ef7b71b03dacd"),
+               },
+               Quorum: 1,
+       }
+}
+
 // Default configurable crosschain parameters.
 func DefaultCrossChainConfig() *CrossChainConfig {
        return &CrossChainConfig{}
@@ -277,6 +293,11 @@ func xpub(str string) (xpub chainkd.XPub) {
        return xpub
 }
 
+// Default configurable oss parameters.
+func DefaultOssConfig() *OssConfig {
+       return &OssConfig{}
+}
+
 //-----------------------------------------------------------------------------
 // Utils
 
index 9e36bc6..699daea 100644 (file)
@@ -33,8 +33,12 @@ func FederationPMultiSigScript(c *Config) []byte {
        return program
 }
 
-func FederationWScript(c *Config) []byte {
+func FederationWScript(c *Config, height uint64) []byte {
        script := FederationPMultiSigScript(c)
+       if height > consensus.CrossInForkHeight {
+               script = FederationPMultiSigScript(&Config{Federation: SingleFederationConfig()})
+       }
+
        scriptHash := crypto.Sha256(script)
        wscript, err := vmutil.P2WSHProgram(scriptHash)
        if err != nil {
@@ -50,7 +54,7 @@ func GenesisTx() *types.Tx {
                log.Panicf("fail on decode genesis tx output control program")
        }
 
-       coinbaseInput := FederationWScript(CommonConfig)
+       coinbaseInput := FederationWScript(CommonConfig, 0)
 
        txData := types.TxData{
                Version: 1,
index d34afa3..f8b0c74 100644 (file)
@@ -32,7 +32,7 @@ var mainNetConfigTmpl = `chain_id = "mainnet"
 laddr = "tcp://0.0.0.0:56656"
 seeds = "47.103.79.68:56656,47.103.13.86:56656,47.102.193.119:56656,47.103.17.22:56656"
 [cross_chain]
-asset_whitelist = "184e1cc4ee4845023888810a79eed7a42c02c544cf2c61ceac05e176d575bd46,78de44ffa1bce37b757c9eae8925b5f199dc4621b412ef0f3f46168865284a93,bda946b3110fa46fd94346ce3f05f0760f1b9de72e238835bc4d19f9d64f1742"
+asset_whitelist = "184e1cc4ee4845023888810a79eed7a42c02c544cf2c61ceac05e176d575bd46,78de44ffa1bce37b757c9eae8925b5f199dc4621b412ef0f3f46168865284a93,bda946b3110fa46fd94346ce3f05f0760f1b9de72e238835bc4d19f9d64f1742,25f2069140fa3ff4d6e0dc1d0fcaa11ace01eb721f115f0f1a5a3782db597fb1,c4644dd6643475d57ed624f63129ab815f282b61f4bb07646d73423a6e1a1563"
 `
 
 var testNetConfigTmpl = `chain_id = "testnet"
index 7a358d0..ec6c6cf 100644 (file)
@@ -14,6 +14,8 @@ const (
        PayToWitnessPubKeyHashDataSize = 20
        PayToWitnessScriptHashDataSize = 32
 
+       CrossInForkHeight = 128957600
+
        _ = iota
        SoftFork001
 )
@@ -156,6 +158,7 @@ var MainNetParams = Params{
        Checkpoints: []Checkpoint{},
        ProducerSubsidys: []ProducerSubsidy{
                {BeginBlock: 1, EndBlock: 63072000, Subsidy: 9512938},
+               {BeginBlock: 63072001, EndBlock: 126144000, Subsidy: 9512938},
        },
        SoftForkPoint:  map[uint64]uint64{SoftFork001: 10461600},
        MovStartHeight: 42884800,
@@ -244,12 +247,12 @@ func BlockSubsidy(height uint64) uint64 {
 
 // BytomMainNetParams is the config for bytom mainnet
 func BytomMainNetParams(vaporParam *Params) *Params {
-       bech32HRPSegwit := "sm"
+       bech32HRPSegwit := "sn"
        switch vaporParam.Name {
        case "main":
-               bech32HRPSegwit = "bm"
+               bech32HRPSegwit = "bn"
        case "test":
-               bech32HRPSegwit = "tm"
+               bech32HRPSegwit = "tn"
        }
        return &Params{Bech32HRPSegwit: bech32HRPSegwit}
 }
index 0396df1..8432dcd 100644 (file)
@@ -6,16 +6,6 @@ import (
        "crypto"
 )
 
-// var currentCrypt XXXXX
-
-// var (
-//     currentCrypto map[string]XXXX
-// )
-
-// func init() {
-//     currentCrypt = currentCrypto[config。?????]
-// }
-
 type XPrvKeyer interface {
        // XPub derives an extended public key from a given xprv.
        XPub() XPubKeyer
diff --git a/docs/release-notes/release-notes-1.1.4.md b/docs/release-notes/release-notes-1.1.4.md
new file mode 100644 (file)
index 0000000..94f8811
--- /dev/null
@@ -0,0 +1,39 @@
+Vapor version 1.1.4 is now available from:
+
+  https://github.com/Bytom/vapor/releases/tag/v1.1.4
+
+
+Please report bugs using the issue tracker at github:
+
+  https://github.com/Bytom/vapor/issues
+
+
+
+1.1.4 changelog
+================
+__Vapor Node__
+
++ `PR #538`
+    - Add the height interval of the next round of block rewards.
++ `PR #536`
+    - Support cross-chain of USDC and DAI.
++ `PR #535`
+    - Fix the bug when the trader only received 1 unit of assets during the match transaction and the magnet contract report error.
+
+
+Credits
+--------
+
+Thanks to everyone who directly contributed to this release:
+
+- Colt-Z
+- HAOYUatHZ
+- ipqhjjybj
+- langyu
+- Paladz
+- shenao78
+- oysheng
+- zcc0721
+- ZhitingLin
+
+And everyone who helped test.
\ No newline at end of file
index e16ceea..5563cd0 100644 (file)
@@ -20,7 +20,10 @@ const (
        maxAge       int64 = 604800
 )
 
-var defaultFormatter = &logrus.TextFormatter{DisableColors: true}
+var defaultFormatter = &logrus.TextFormatter{
+       DisableColors:   true,
+       TimestampFormat: time.StampMicro,
+}
 
 func InitLogFile(config *config.Config) error {
        logPath := config.LogDir()
index 9cdfe1b..0d5a40e 100644 (file)
@@ -7,8 +7,10 @@ import (
 
        "github.com/bytom/vapor/consensus"
        dbm "github.com/bytom/vapor/database/leveldb"
+       "github.com/bytom/vapor/errors"
        "github.com/bytom/vapor/netsync/peers"
        "github.com/bytom/vapor/p2p/security"
+       "github.com/bytom/vapor/protocol"
        "github.com/bytom/vapor/protocol/bc"
        "github.com/bytom/vapor/protocol/bc/types"
 )
@@ -22,7 +24,7 @@ const (
 )
 
 var (
-       maxNumOfBlocksPerMsg      = uint64(1000)
+       maxNumOfBlocksPerMsg      = uint64(64)
        maxNumOfHeadersPerMsg     = uint64(1000)
        maxNumOfBlocksRegularSync = uint64(128)
 )
@@ -170,7 +172,9 @@ func (bk *blockKeeper) regularBlockSync() error {
 
                isOrphan, err := bk.chain.ProcessBlock(block)
                if err != nil {
-                       bk.peers.ProcessIllegal(bk.syncPeer.ID(), security.LevelMsgIllegal, err.Error())
+                       if errors.Root(err) != protocol.ErrDoubleSignBlock {
+                               bk.peers.ProcessIllegal(bk.syncPeer.ID(), security.LevelMsgIllegal, err.Error())
+                       }
                        return err
                }
 
@@ -199,7 +203,7 @@ func (bk *blockKeeper) checkSyncType() int {
        bestHeight := bk.chain.BestBlockHeight()
        peer := bk.peers.BestIrreversiblePeer(consensus.SFFullNode | consensus.SFFastSync)
        if peer != nil {
-               if peerIrreversibleHeight := peer.IrreversibleHeight(); peerIrreversibleHeight >= bestHeight+minGapStartFastSync {
+               if peerIrreversibleHeight := peer.Height(); peerIrreversibleHeight >= bestHeight+minGapStartFastSync {
                        bk.fastSync.setSyncPeer(peer)
                        return fastSyncType
                }
index 50a110b..f7f4619 100644 (file)
@@ -8,6 +8,7 @@ import (
        "github.com/bytom/vapor/errors"
        "github.com/bytom/vapor/netsync/peers"
        "github.com/bytom/vapor/p2p/security"
+       "github.com/bytom/vapor/protocol"
 )
 
 var errOrphanBlock = errors.New("fast sync inserting orphan block")
@@ -33,7 +34,7 @@ func (bp *blockProcessor) insert(blockStorage *blockStorage) error {
                return errOrphanBlock
        }
 
-       if err != nil {
+       if err != nil && errors.Root(err) != protocol.ErrDoubleSignBlock {
                bp.peers.ProcessIllegal(blockStorage.peerID, security.LevelMsgIllegal, err.Error())
        }
        return err
index 35d1f9a..152367d 100644 (file)
@@ -157,13 +157,18 @@ func (m *Manager) handleGetBlockMsg(peer *peers.Peer, msg *msgs.GetBlockMessage)
 }
 
 func (m *Manager) handleGetBlocksMsg(peer *peers.Peer, msg *msgs.GetBlocksMessage) {
-       endTime := time.Now().Add(requireBlocksTimeout / 2)
+       endTime := time.Now().Add(requireBlocksTimeout / 10)
        isTimeout := func() bool {
                return time.Now().After(endTime)
        }
 
        blocks, err := m.blockKeeper.locateBlocks(msg.GetBlockLocator(), msg.GetStopHash(), isTimeout)
        if err != nil || len(blocks) == 0 {
+               log.WithFields(log.Fields{
+                       "module": logModule,
+                       "err":    err,
+                       "size":   len(blocks),
+               }).Error("fail on handleGetBlocksMsg locateBlocks")
                return
        }
 
@@ -264,7 +269,7 @@ func (m *Manager) handleTransactionMsg(peer *peers.Peer, msg *msgs.TransactionMe
        }
 
        if m.mempool.IsDust(tx) {
-               m.peers.ProcessIllegal(peer.ID(), security.LevelMsgIllegal, "receive dust tx msg")
+               log.WithFields(log.Fields{"tx_hash": tx.ID.String(), "peer": peer.Addr()}).Warn("receive dust tx msg")
                return
        }
 
index a397dd5..5865098 100644 (file)
@@ -5,10 +5,12 @@ import (
 
        "github.com/sirupsen/logrus"
 
+       "github.com/bytom/vapor/errors"
        "github.com/bytom/vapor/event"
        "github.com/bytom/vapor/netsync/peers"
        "github.com/bytom/vapor/p2p"
        "github.com/bytom/vapor/p2p/security"
+       "github.com/bytom/vapor/protocol"
        "github.com/bytom/vapor/protocol/bc"
        "github.com/bytom/vapor/protocol/bc/types"
 )
@@ -109,8 +111,9 @@ func (m *Manager) handleBlockSignatureMsg(peerID string, msg *BlockSignatureMsg)
        m.peers.MarkBlockSignature(peerID, msg.Signature)
        blockHash := bc.NewHash(msg.BlockHash)
        if err := m.chain.ProcessBlockSignature(msg.Signature, msg.PubKey, &blockHash); err != nil {
-               m.peers.ProcessIllegal(peerID, security.LevelMsgIllegal, err.Error())
-               return
+               if errors.Root(err) != protocol.ErrDoubleSignBlock {
+                       m.peers.ProcessIllegal(peerID, security.LevelMsgIllegal, err.Error())
+               }
        }
 }
 
index 61e2b8c..87d7456 100644 (file)
@@ -71,7 +71,7 @@ func NewNode(config *cfg.Config) *Node {
                "pubkey":             config.PrivateKey().XPub(),
                "fed_xpubs":          config.Federation.Xpubs,
                "fed_quorum":         config.Federation.Quorum,
-               "fed_controlprogram": hex.EncodeToString(cfg.FederationWScript(config)),
+               "fed_controlprogram": hex.EncodeToString(cfg.FederationWScript(config, 0)),
        }).Info()
 
        // Get store
@@ -228,7 +228,7 @@ func initNodeConfig(config *cfg.Config) error {
 
 // find whether config xpubs equal genesis block xpubs
 func checkConfig(chain *protocol.Chain, config *cfg.Config) error {
-       fedpegScript := cfg.FederationWScript(config)
+       fedpegScript := cfg.FederationWScript(config, 0)
        genesisBlock, err := chain.GetBlockByHeight(0)
        if err != nil {
                return err
@@ -326,3 +326,8 @@ func (n *Node) RunForever() {
                n.Stop()
        })
 }
+
+// GetChain return the chain
+func (n *Node) GetChain() *protocol.Chain {
+       return n.chain
+}
index f7d08b0..864c382 100644 (file)
@@ -17,9 +17,9 @@ import (
 
 const (
        logModule         = "blockproposer"
-       warnTimeNum       = 2
+       warnTimeNum       = 1
        warnTimeDenom     = 5
-       criticalTimeNum   = 4
+       criticalTimeNum   = 2
        criticalTimeDenom = 5
 )
 
index 2186249..ea0cc62 100644 (file)
@@ -21,7 +21,8 @@ import (
 
 const (
        logModule     = "mining"
-       batchApplyNum = 64
+       batchApplyNum = 16
+       softMaxTxNum  = 128
 
        timeoutOk = iota + 1
        timeoutWarn
@@ -119,7 +120,7 @@ func (b *blockBuilder) applyTransactions(txs []*types.Tx, timeoutStatus uint8) e
 
                b.gasLeft = gasLeft
                tempTxs = []*types.Tx{}
-               if b.getTimeoutStatus() >= timeoutStatus {
+               if b.getTimeoutStatus() >= timeoutStatus || len(b.block.Transactions) > softMaxTxNum {
                        break
                }
        }
@@ -148,7 +149,7 @@ func (b *blockBuilder) applyTransactionFromSubProtocol() error {
                        break
                }
 
-               subTxs, err := p.BeforeProposalBlock(b.block.Transactions, b.block.Height, b.gasLeft, isTimeout)
+               subTxs, err := p.BeforeProposalBlock(b.block, b.gasLeft, isTimeout)
                if err != nil {
                        log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package")
                        continue
index cd9fccd..fe7ec25 100644 (file)
@@ -3,7 +3,7 @@ package protocol
 import (
        "strings"
 
-       "github.com/bytom/vapor/consensus"
+       "github.com/bytom/vapor/common"
        "github.com/bytom/vapor/protocol/bc/types"
 )
 
@@ -16,7 +16,6 @@ type AssetFilter struct {
 // which is a strings list cancated via comma
 func NewAssetFilter(whitelist string) *AssetFilter {
        af := &AssetFilter{whitelist: make(map[string]struct{})}
-       af.whitelist[consensus.BTMAssetID.String()] = struct{}{}
        for _, assetID := range strings.Split(whitelist, ",") {
                af.whitelist[strings.ToLower(assetID)] = struct{}{}
        }
@@ -28,7 +27,7 @@ func NewAssetFilter(whitelist string) *AssetFilter {
 // No need to check the output assets types becauese they must have been cover in input assets types
 func (af *AssetFilter) IsDust(tx *types.Tx) bool {
        for _, input := range tx.Inputs {
-               if _, ok := input.TypedInput.(*types.CrossChainInput); !ok {
+               if crossChainInput, ok := input.TypedInput.(*types.CrossChainInput); !ok || !common.IsOpenFederationIssueAsset(crossChainInput.AssetDefinition) {
                        continue
                }
 
diff --git a/protocol/asset_filter_test.go b/protocol/asset_filter_test.go
new file mode 100644 (file)
index 0000000..7897942
--- /dev/null
@@ -0,0 +1,64 @@
+package protocol
+
+import (
+       "testing"
+
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/testutil"
+)
+
+func TestIsDust(t *testing.T) {
+       assetFilter := NewAssetFilter("184e1cc4ee4845023888810a79eed7a42c02c544cf2c61ceac05e176d575bd46")
+       cases := []struct {
+               tx         *types.Tx
+               wantIsDust bool
+       }{
+               {
+                       tx: types.NewTx(types.TxData{
+                               Inputs: []*types.TxInput{
+                                       types.NewCrossChainInput(nil, bc.Hash{}, testutil.MustDecodeAsset("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), 1e8, 1, 1, []byte("{\n  \"decimals\": 8,\n  \"description\": \"Bytom Official Issue\",\n  \"name\": \"BTM\",\n  \"symbol\": \"BTM\"\n}"), []byte("assetbtm"))},
+                               Outputs: []*types.TxOutput{
+                                       types.NewIntraChainOutput(testutil.MustDecodeAsset("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), 1e8, []byte{0x51}),
+                               },
+                       }),
+                       wantIsDust: false,
+               },
+               {
+                       tx: types.NewTx(types.TxData{
+                               Inputs: []*types.TxInput{
+                                       types.NewCrossChainInput(nil, bc.Hash{}, testutil.MustDecodeAsset("184e1cc4ee4845023888810a79eed7a42c02c544cf2c61ceac05e176d575bd46"), 1e8, 1, 1, []byte("{\n  \"decimals\": 6,\n  \"description\": {\n    \"issue_asset_action\": \"open_federation_cross_chain\"\n  },\n  \"name\": \"USDT\",\n  \"quorum\": \"3\",\n  \"reissue\": \"true\",\n  \"symbol\": \"USDT\"\n}"), []byte("assetusdt"))},
+                               Outputs: []*types.TxOutput{
+                                       types.NewIntraChainOutput(testutil.MustDecodeAsset("184e1cc4ee4845023888810a79eed7a42c02c544cf2c61ceac05e176d575bd46"), 1e8, []byte{0x51}),
+                               },
+                       }),
+                       wantIsDust: false,
+               },
+               {
+                       tx: types.NewTx(types.TxData{
+                               Inputs: []*types.TxInput{
+                                       types.NewCrossChainInput(nil, bc.Hash{}, testutil.MustDecodeAsset("47fcd4d7c22d1d38931a6cd7767156babbd5f05bbbb3f7d3900635b56eb1b67e"), 1e8, 1, 1, []byte("{\n  \"decimals\": 8,\n  \"description\": {},\n  \"name\": \"SUP\",\n  \"quorum\": 1,\n  \"reissue\": \"false\",\n  \"symbol\": \"SUP\"\n}"), []byte("assetsup"))},
+                               Outputs: []*types.TxOutput{
+                                       types.NewIntraChainOutput(testutil.MustDecodeAsset("47fcd4d7c22d1d38931a6cd7767156babbd5f05bbbb3f7d3900635b56eb1b67e"), 1e8, []byte{0x51}),
+                               },
+                       }),
+                       wantIsDust: false,
+               },
+               {
+                       tx: types.NewTx(types.TxData{
+                               Inputs: []*types.TxInput{
+                                       types.NewCrossChainInput(nil, bc.Hash{}, testutil.MustDecodeAsset("c4644dd6643475d57ed624f63129ab815f282b61f4bb07646d73423a6e1a1563"), 1e8, 1, 1, []byte("{\n\"decimals\":6,\n\"description\":{\n\"issue_asset_action\":\"open_federation_cross_chain\"\n},\n\"name\":\"USDC\",\n\"quorum\":\"3\",\n\"reissue\":\"true\",\n\"symbol\":\"USDC\"\n}"), []byte("assetusdc"))},
+                               Outputs: []*types.TxOutput{
+                                       types.NewIntraChainOutput(testutil.MustDecodeAsset("c4644dd6643475d57ed624f63129ab815f282b61f4bb07646d73423a6e1a1563"), 1e8, []byte{0x51}),
+                               },
+                       }),
+                       wantIsDust: true,
+               },
+       }
+
+       for i, c := range cases {
+               if gotIsDust := assetFilter.IsDust(c.tx); gotIsDust != c.wantIsDust {
+                       t.Errorf("case %d: fail on AssetFilter TestIsDust", i)
+               }
+       }
+}
index 02accaf..e1fa489 100644 (file)
@@ -20,9 +20,9 @@ const (
 )
 
 var (
-       errDoubleSignBlock  = errors.New("the consensus is double sign in same height of different block")
+       // ErrDoubleSignBlock represent the consensus is double sign in same height of different block
+       ErrDoubleSignBlock  = errors.New("the consensus is double sign in same height of different block")
        errInvalidSignature = errors.New("the signature of block is invalid")
-       errSignForkChain    = errors.New("can not sign fork before the irreversible block")
 )
 
 func signCacheKey(blockHash, pubkey string) string {
@@ -53,7 +53,7 @@ func (c *Chain) checkDoubleSign(bh *types.BlockHeader, xPub string) error {
                }
 
                if blockHeader.BlockWitness.Get(consensusNode.Order) != nil {
-                       return errDoubleSignBlock
+                       return ErrDoubleSignBlock
                }
        }
        return nil
@@ -140,7 +140,7 @@ func (c *Chain) validateSign(block *types.Block) error {
                        }
                }
 
-               if err := c.checkNodeSign(&block.BlockHeader, node, block.Get(node.Order)); err == errDoubleSignBlock {
+               if err := c.checkNodeSign(&block.BlockHeader, node, block.Get(node.Order)); err == ErrDoubleSignBlock {
                        log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String(), "pubKey": pubKey}).Warn("the consensus node double sign the same height of different block")
                        // if the blocker double sign & become the mainchain, that means
                        // all the side chain will reject the main chain make the chain
@@ -149,6 +149,8 @@ func (c *Chain) validateSign(block *types.Block) error {
                                block.BlockWitness.Delete(node.Order)
                                continue
                        }
+
+                       return err
                } else if err != nil {
                        return err
                }
@@ -246,7 +248,7 @@ func (c *Chain) signBlockHeader(blockHeader *types.BlockHeader) ([]byte, error)
                return nil, nil
        }
 
-       if err := c.checkDoubleSign(blockHeader, node.XPub.String()); err == errDoubleSignBlock {
+       if err := c.checkDoubleSign(blockHeader, node.XPub.String()); err == ErrDoubleSignBlock {
                log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String()}).Warn("current node has double sign the block")
                return nil, nil
        } else if err != nil {
index 52eab1b..ab690ba 100644 (file)
@@ -224,15 +224,26 @@ func (c *Chain) Rollback(targetHeight uint64) error {
        }
 
        startSeq := state.CalcVoteSeq(c.bestBlockHeader.Height)
-
        if err = c.setState(targetBlockHeader, setIrrBlockHeader, nil, utxoView, []*state.ConsensusResult{consensusResult.Fork()}); err != nil {
                return err
        }
 
        for _, block := range deletedBlocks {
-               if err := c.store.DeleteBlock(block); err != nil {
+               hashes, err := c.store.GetBlockHashesByHeight(block.Height)
+               if err != nil{
                        return err
                }
+
+               for _, hash := range hashes{
+                       block, err := c.store.GetBlock(hash)
+                       if err != nil{
+                               return err
+                       }
+
+                       if err := c.store.DeleteBlock(block); err != nil{
+                               return err
+                       }
+               }
        }
 
        endSeq := state.CalcVoteSeq(targetHeight)
@@ -331,10 +342,9 @@ func (c *Chain) reorganizeChain(blockHeader *types.BlockHeader) error {
                log.WithFields(log.Fields{"module": logModule, "height": blockHeader.Height, "hash": blockHash.String()}).Debug("attach from mainchain")
        }
 
-       if len(detachBlockHeaders) > 0 &&
-               detachBlockHeaders[len(detachBlockHeaders)-1].Height <= c.lastIrrBlockHeader.Height &&
-               irrBlockHeader.Height <= c.lastIrrBlockHeader.Height {
-               return errors.New("rollback block below the height of irreversible block")
+       if len(detachBlockHeaders) > 0 && detachBlockHeaders[len(detachBlockHeaders)-1].Height <= c.lastIrrBlockHeader.Height && irrBlockHeader.Height <= c.lastIrrBlockHeader.Height {
+               log.WithField("module", logModule).Warn("rollback block below the height of irreversible block")
+               return nil
        }
 
        consensusResults = append(consensusResults, consensusResult.Fork())
index 6b4668e..0f9429d 100644 (file)
@@ -84,18 +84,12 @@ func (c *Chain) getConsensusResult(seq uint64, blockHeader *types.BlockHeader) (
 }
 
 func (c *Chain) getPrevRoundLastBlock(prevBlockHash *bc.Hash) (*types.BlockHeader, error) {
-       blockHeader, err := c.store.GetBlockHeader(prevBlockHash)
+       preRoundBlockHash, err := c.getPrevRoundVoteBlockHash(prevBlockHash)
        if err != nil {
-               return nil, errNotFoundBlockNode
+               return nil, err
        }
 
-       for blockHeader.Height%consensus.ActiveNetParams.RoundVoteBlockNums != 0 {
-               blockHeader, err = c.store.GetBlockHeader(&blockHeader.PreviousBlockHash)
-               if err != nil {
-                       return nil, err
-               }
-       }
-       return blockHeader, nil
+       return c.store.GetBlockHeader(preRoundBlockHash)
 }
 
 func (c *Chain) reorganizeConsensusResult(consensusResult *state.ConsensusResult, blockHeader *types.BlockHeader) error {
index 6808d81..bcb8925 100644 (file)
@@ -80,6 +80,9 @@ func (o *OrphanManage) Get(hash *bc.Hash) (*types.Block, bool) {
        o.mtx.RLock()
        block, ok := o.orphan[*hash]
        o.mtx.RUnlock()
+       if !ok {
+               return nil, ok
+       }
        return block.Block, ok
 }
 
index ea1b2b0..90c531f 100644 (file)
@@ -7,6 +7,7 @@ import (
 
        "github.com/bytom/vapor/common"
        "github.com/bytom/vapor/config"
+       "github.com/bytom/vapor/consensus"
        "github.com/bytom/vapor/errors"
        "github.com/bytom/vapor/event"
        "github.com/bytom/vapor/protocol/bc"
@@ -15,8 +16,9 @@ import (
 )
 
 const (
-       maxProcessBlockChSize = 1024
-       maxKnownTxs           = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
+       maxProcessBlockChSize              = 1024
+       maxKnownTxs                        = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
+       maxPrevRoundVoteBlockHashCacheSize = 32768
 )
 
 // ErrNotInitSubProtocolChainStatus represent the node state of sub protocol has not been initialized
@@ -26,7 +28,7 @@ var ErrNotInitSubProtocolChainStatus = errors.New("node state of sub protocol ha
 type SubProtocol interface {
        Name() string
        StartHeight() uint64
-       BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
+       BeforeProposalBlock(block *types.Block, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
 
        // ChainStatus return the the current block height and block hash of sub protocol.
        // it will return ErrNotInitSubProtocolChainStatus if not initialized.
@@ -46,8 +48,9 @@ type Chain struct {
        processBlockCh chan *processBlockMsg
        subProtocols   []SubProtocol
 
-       signatureCache  *common.Cache
-       eventDispatcher *event.Dispatcher
+       signatureCache              *common.Cache
+       prevRoundVoteBlockHashCache *common.Cache
+       eventDispatcher             *event.Dispatcher
 
        cond               sync.Cond
        bestBlockHeader    *types.BlockHeader // the last block on current main chain
@@ -60,14 +63,15 @@ type Chain struct {
 func NewChain(store Store, txPool *TxPool, subProtocols []SubProtocol, eventDispatcher *event.Dispatcher) (*Chain, error) {
        knownTxs, _ := common.NewOrderedSet(maxKnownTxs)
        c := &Chain{
-               orphanManage:    NewOrphanManage(),
-               txPool:          txPool,
-               store:           store,
-               subProtocols:    subProtocols,
-               signatureCache:  common.NewCache(maxSignatureCacheSize),
-               eventDispatcher: eventDispatcher,
-               processBlockCh:  make(chan *processBlockMsg, maxProcessBlockChSize),
-               knownTxs:        knownTxs,
+               orphanManage:                NewOrphanManage(),
+               txPool:                      txPool,
+               store:                       store,
+               subProtocols:                subProtocols,
+               signatureCache:              common.NewCache(maxSignatureCacheSize),
+               prevRoundVoteBlockHashCache: common.NewCache(maxPrevRoundVoteBlockHashCacheSize),
+               eventDispatcher:             eventDispatcher,
+               processBlockCh:              make(chan *processBlockMsg, maxProcessBlockChSize),
+               knownTxs:                    knownTxs,
        }
        c.cond.L = new(sync.Mutex)
 
@@ -137,6 +141,39 @@ func (c *Chain) initChainStatus() error {
        return c.store.SaveChainStatus(genesisBlockHeader, genesisBlockHeader, []*types.BlockHeader{genesisBlockHeader}, utxoView, consensusResults)
 }
 
+// getPrevRoundVoteBlockHash return the previous round block hash by the given block header
+func (c *Chain) getPrevRoundVoteBlockHash(hash *bc.Hash) (*bc.Hash, error) {
+       if data, ok := c.prevRoundVoteBlockHashCache.Get(*hash); ok {
+               return data.(*bc.Hash), nil
+       }
+
+       header, err := c.store.GetBlockHeader(hash)
+       if err != nil {
+               return nil, errNotFoundBlockNode
+       }
+
+       if header.Height%consensus.ActiveNetParams.RoundVoteBlockNums == 0 {
+               c.prevRoundVoteBlockHashCache.Add(*hash, hash)
+               return hash, nil
+       }
+
+       if data, ok := c.prevRoundVoteBlockHashCache.Get(header.PreviousBlockHash); ok {
+               c.prevRoundVoteBlockHashCache.Add(*hash, data.(*bc.Hash))
+               return data.(*bc.Hash), nil
+       }
+
+       // loop find the prev round vote block hash
+       for header.Height%consensus.ActiveNetParams.RoundVoteBlockNums != 0 {
+               header, err = c.store.GetBlockHeader(&header.PreviousBlockHash)
+               if err != nil {
+                       return nil, err
+               }
+       }
+       preRoundVoteBlockHash := header.Hash()
+       c.prevRoundVoteBlockHashCache.Add(*hash, &preRoundVoteBlockHash)
+       return &preRoundVoteBlockHash, nil
+}
+
 // BestBlockHeight returns the current height of the blockchain.
 func (c *Chain) BestBlockHeight() uint64 {
        c.cond.L.Lock()
index 1206916..55800fe 100644 (file)
@@ -29,7 +29,7 @@ func (c *Chain) ValidateTx(tx *types.Tx) (bool, error) {
 
        bh := c.BestBlockHeader()
        isOrphan, err := c.validateTx(tx, bh)
-       if err == nil {
+       if err == nil && !isOrphan {
                c.markTransactions(tx)
        }
        return isOrphan, err
index 50e448e..68e2a80 100644 (file)
@@ -5,6 +5,7 @@ import (
        "time"
 
        "github.com/davecgh/go-spew/spew"
+       log "github.com/sirupsen/logrus"
 
        "github.com/bytom/vapor/consensus"
        "github.com/bytom/vapor/database/storage"
@@ -757,3 +758,7 @@ func TestProcessTransaction(t *testing.T) {
                }
        }
 }
+
+func init() {
+       log.SetLevel(log.ErrorLevel)
+}
index 4292014..e9938c6 100644 (file)
@@ -1,8 +1,6 @@
 package validation
 
 import (
-       "bytes"
-       "encoding/hex"
        "time"
 
        log "github.com/sirupsen/logrus"
@@ -45,28 +43,22 @@ func checkCoinbaseTx(b *bc.Block, rewards []state.CoinbaseReward) error {
        }
 
        tx := b.Transactions[0]
-       if len(tx.TxHeader.ResultIds) != len(rewards)+1 {
-               return errors.Wrapf(ErrWrongCoinbaseTransaction, "dismatch number of outputs, got:%d, want:%d", len(tx.TxHeader.ResultIds), len(rewards))
-       }
-
-       rewards = append([]state.CoinbaseReward{state.CoinbaseReward{Amount: uint64(0)}}, rewards...)
-       for i, output := range tx.TxHeader.ResultIds {
+       var rewardAmount, coinbaseAmount uint64
+       for _, output := range tx.TxHeader.ResultIds {
                out, err := tx.IntraChainOutput(*output)
                if err != nil {
                        return err
                }
 
-               if rewards[i].Amount != out.Source.Value.Amount {
-                       return errors.Wrapf(ErrWrongCoinbaseTransaction, "dismatch output amount, got:%d, want:%d", out.Source.Value.Amount, rewards[i].Amount)
-               }
+               coinbaseAmount += out.Source.Value.Amount
+       }
 
-               if i == 0 {
-                       continue
-               }
+       for _, reward := range rewards {
+               rewardAmount += reward.Amount
+       }
 
-               if res := bytes.Compare(rewards[i].ControlProgram, out.ControlProgram.Code); res != 0 {
-                       return errors.Wrapf(ErrWrongCoinbaseTransaction, "dismatch output control_program, got:%s, want:%s", hex.EncodeToString(out.ControlProgram.Code), hex.EncodeToString(rewards[i].ControlProgram))
-               }
+       if rewardAmount != coinbaseAmount {
+               return errors.Wrapf(ErrWrongCoinbaseTransaction, "dismatch output amount, got:%d, want:%d", coinbaseAmount, rewardAmount)
        }
        return nil
 }
index fdc1a4d..686c154 100644 (file)
@@ -159,30 +159,6 @@ func TestCheckCoinbaseTx(t *testing.T) {
                        },
                        err: ErrWrongCoinbaseTransaction,
                },
-               {
-                       desc: "wrong coinbase transaction with dismatch output control_program",
-                       txs: []*types.Tx{
-                               types.NewTx(types.TxData{
-                                       Inputs: []*types.TxInput{types.NewCoinbaseInput(nil)},
-                                       Outputs: []*types.TxOutput{
-                                               types.NewIntraChainOutput(*consensus.BTMAssetID, 0, []byte{0x51}),
-                                               types.NewIntraChainOutput(*consensus.BTMAssetID, 20000, []byte{0x51}),
-                                               types.NewIntraChainOutput(*consensus.BTMAssetID, 10000, []byte{0x52}),
-                                       },
-                               }),
-                       },
-                       rewards: []state.CoinbaseReward{
-                               state.CoinbaseReward{
-                                       Amount:         20000,
-                                       ControlProgram: []byte{0x51},
-                               },
-                               state.CoinbaseReward{
-                                       Amount:         10000,
-                                       ControlProgram: []byte{0x53},
-                               },
-                       },
-                       err: ErrWrongCoinbaseTransaction,
-               },
        }
 
        block := new(types.Block)
@@ -481,44 +457,6 @@ func TestValidateBlock(t *testing.T) {
                        err: ErrWrongCoinbaseTransaction,
                },
                {
-                       desc: "the coinbase program is not equal to the real coinbase outputs",
-                       block: &bc.Block{
-                               ID: bc.Hash{V0: 1},
-                               BlockHeader: &bc.BlockHeader{
-                                       Version:               1,
-                                       Height:                1,
-                                       Timestamp:             1523352601000,
-                                       PreviousBlockId:       &parentHash,
-                                       TransactionsRoot:      &txsRoot,
-                                       TransactionStatusHash: &txStatusHash,
-                               },
-                               Transactions: []*bc.Tx{
-                                       types.MapTx(&types.TxData{
-                                               Version:        1,
-                                               SerializedSize: 1,
-                                               Inputs:         []*types.TxInput{types.NewCoinbaseInput(nil)},
-                                               Outputs: []*types.TxOutput{
-                                                       types.NewIntraChainOutput(*consensus.BTMAssetID, 0, cp),
-                                                       types.NewIntraChainOutput(*consensus.BTMAssetID, 20000, []byte{0x51}),
-                                                       types.NewIntraChainOutput(*consensus.BTMAssetID, 10000, []byte{0x61}),
-                                               },
-                                       }),
-                               },
-                       },
-                       parent: parent,
-                       rewards: []state.CoinbaseReward{
-                               state.CoinbaseReward{
-                                       Amount:         20000,
-                                       ControlProgram: []byte{0x51},
-                               },
-                               state.CoinbaseReward{
-                                       Amount:         10000,
-                                       ControlProgram: []byte{0x52},
-                               },
-                       },
-                       err: ErrWrongCoinbaseTransaction,
-               },
-               {
                        desc: "the coinbase amount is equal to the real coinbase amount",
                        block: &bc.Block{
                                ID: bc.Hash{V0: 1},
index 44287e9..a9613b5 100644 (file)
@@ -281,7 +281,7 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                }
 
                if !common.IsOpenFederationIssueAsset(e.RawDefinitionByte) {
-                       prog.Code = config.FederationWScript(config.CommonConfig)
+                       prog.Code = config.FederationWScript(config.CommonConfig, vs.block.Height)
                }
 
                if _, err := vm.Verify(NewTxVMContext(vs, e, prog, e.WitnessArguments), consensus.ActiveNetParams.DefaultGasCredit); err != nil {
index 21cc0d4..8446142 100644 (file)
@@ -167,7 +167,7 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
 //                           sellerKey: PublicKey) locks valueAmount of valueAsset {
 //  clause partialTrade(exchangeAmount: Amount) {
 //   define actualAmount: Integer = exchangeAmount * ratioDenominator / ratioNumerator
-//   verify actualAmount > 0 && actualAmount < valueAmount
+//   verify actualAmount >= 0 && actualAmount < valueAmount
 //   define receiveAmount: Integer = exchangeAmount * 999 / 1000
 //   lock receiveAmount of requestedAsset with sellerProgram
 //   lock valueAmount-actualAmount of valueAsset with standardProgram
@@ -176,7 +176,7 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
 //  clause fullTrade() {
 //   define requestedAmount: Integer = valueAmount * ratioNumerator / ratioDenominator
 //   define requestedAmount: Integer = requestedAmount * 999 / 1000
-//   verify requestedAmount > 0
+//   verify requestedAmount >= 0
 //   lock requestedAmount of requestedAsset with sellerProgram
 //   unlock valueAmount of valueAsset
 //  }
@@ -208,7 +208,7 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
 // AMOUNT                   [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount]
 // OVER                     [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount actualAmount]
 // 0                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount actualAmount 0]
-// GREATERTHAN              [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0)]
+// GREATERTHANOREQUAL       [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0)]
 // 2                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) 2]
 // PICK                     [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) actualAmount]
 // 2                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) actualAmount 2]
@@ -256,7 +256,7 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
 // MULFRACTION              [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
 // DUP                      [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount]
 // 0                        [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount 0]
-// GREATERTHAN              [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount (requestedAmount > 0)]
+// GREATERTHANOREQUAL       [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount (requestedAmount > 0)]
 // VERIFY                   [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
 // FROMALTSTACK             [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount <position>]
 // SWAP                     [... sellerKey standardProgram sellerProgram requestedAsset <position> requestedAmount]
@@ -311,7 +311,7 @@ func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
        builder.AddOp(vm.OP_AMOUNT)
        builder.AddOp(vm.OP_OVER)
        builder.AddOp(vm.OP_0)
-       builder.AddOp(vm.OP_GREATERTHAN)
+       builder.AddOp(vm.OP_GREATERTHANOREQUAL)
        builder.AddOp(vm.OP_2)
        builder.AddOp(vm.OP_PICK)
        builder.AddOp(vm.OP_ROT)
@@ -356,7 +356,7 @@ func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
        builder.AddOp(vm.OP_MULFRACTION)
        builder.AddOp(vm.OP_DUP)
        builder.AddOp(vm.OP_0)
-       builder.AddOp(vm.OP_GREATERTHAN)
+       builder.AddOp(vm.OP_GREATERTHANOREQUAL)
        builder.AddOp(vm.OP_VERIFY)
        builder.AddOp(vm.OP_FROMALTSTACK)
        builder.AddOp(vm.OP_SWAP)
index 7b6ae94..8a7cf5a 100644 (file)
@@ -16,6 +16,16 @@ func (n *Node) GetBlockByHeight(height uint64) (*types.Block, error) {
        return n.getRawBlock(&getRawBlockReq{BlockHeight: height})
 }
 
+type getBlockCountResp struct {
+       BlockCount uint64 `json:"block_count"`
+}
+
+func (n *Node) GetBlockCount() (uint64, error) {
+       url := "/get-block-count"
+       res := &getBlockCountResp{}
+       return res.BlockCount, n.request(url, nil, res)
+}
+
 type getRawBlockReq struct {
        BlockHeight uint64 `json:"block_height"`
        BlockHash   string `json:"block_hash"`
index 2db9fef..699b496 100644 (file)
@@ -1,8 +1,6 @@
 package consensusreward
 
 import (
-       "math/big"
-
        log "github.com/sirupsen/logrus"
 
        "github.com/bytom/vapor/consensus"
@@ -12,7 +10,10 @@ import (
        "github.com/bytom/vapor/toolbar/consensusreward/config"
 )
 
-const standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
+const (
+       standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
+       StandbyNodeNum                      = 32
+)
 
 type StandbyNodeReward struct {
        cfg         *config.Config
@@ -39,16 +40,8 @@ func (s *StandbyNodeReward) getStandbyNodeReward(height uint64) error {
        }
 
        voteInfos = common.CalcStandByNodes(voteInfos)
-       totalVoteNum := uint64(0)
-       for _, voteInfo := range voteInfos {
-               totalVoteNum += voteInfo.VoteNum
-       }
-
-       total := big.NewInt(0).SetUint64(totalVoteNum)
        for _, voteInfo := range voteInfos {
-               amount := big.NewInt(0).SetUint64(standbyNodesRewardForConsensusCycle)
-               voteNum := big.NewInt(0).SetUint64(voteInfo.VoteNum)
-               s.xpubRewards[voteInfo.Vote] += amount.Mul(amount, voteNum).Div(amount, total).Uint64()
+               s.xpubRewards[voteInfo.Vote] += standbyNodesRewardForConsensusCycle / StandbyNodeNum
        }
        return nil
 }
index 5ca0042..48aac54 100644 (file)
@@ -3,6 +3,7 @@ package service
 import (
        "encoding/json"
 
+       "github.com/bytom/vapor/api"
        "github.com/bytom/vapor/errors"
        "github.com/bytom/vapor/protocol/bc"
        "github.com/bytom/vapor/toolbar/common"
@@ -36,6 +37,12 @@ func (n *Node) GetBlockCount() (uint64, error) {
        return res.BlockCount, n.request(url, nil, res)
 }
 
+func (n *Node) GetNetInfo() (*api.NetInfo, error) {
+       url := "/net-info"
+       res := &api.NetInfo{}
+       return res, n.request(url, nil, res)
+}
+
 type getRawBlockReq struct {
        BlockHeight uint64 `json:"block_height"`
        BlockHash   string `json:"block_hash"`
index 0e51314..707fdfc 100644 (file)
@@ -270,12 +270,12 @@ func (s *sidechainKeeper) syncBlock() (bool, error) {
                return false, errors.Wrap(err, "query chain")
        }
 
-       height, err := s.node.GetBlockCount()
+       netInfo, err := s.node.GetNetInfo()
        if err != nil {
                return false, err
        }
 
-       if height <= chain.BlockHeight+s.cfg.Confirmations {
+       if netInfo.IrreversibleBlock <= chain.BlockHeight+s.cfg.Confirmations {
                return false, nil
        }
 
diff --git a/toolbar/measure/README.md b/toolbar/measure/README.md
new file mode 100644 (file)
index 0000000..8661be1
--- /dev/null
@@ -0,0 +1,21 @@
+Measure is a tool for help precise analysis of the performance of the goroutine, and it's designed for thread-safe. 
+Just simply call Start() and defer End() on top of the function it will handle all the stack issue and print the tree
+struct result when the root function is ended.
+
+#sample usage
+func processBlock() {
+       measure.Start()
+       defer measure.End()
+
+       ......
+}
+
+
+#sample result
+|--github.com/bytom/vapor/protocol.(*Chain).processBlock: 9.009746ms (100.00)
+  |--github.com/bytom/vapor/protocol.(*Chain).saveBlock: 8.086023ms (89.75)
+    |--github.com/bytom/vapor/protocol.(*Chain).validateSign: 1.443966ms (17.86)
+    |--github.com/bytom/vapor/protocol/validation.ValidateBlock: 195.333µs (2.42)
+      |--github.com/bytom/vapor/protocol/validation.ValidateBlockHeader: 26.48µs (13.56)
+      |--github.com/bytom/vapor/protocol/validation.ValidateTxs: 88.312µs (45.21)
+  |--github.com/bytom/vapor/protocol.(*Chain).connectBlock: 767.073µs (8.51)
\ No newline at end of file
diff --git a/toolbar/measure/method.go b/toolbar/measure/method.go
new file mode 100644 (file)
index 0000000..dbb3b63
--- /dev/null
@@ -0,0 +1,83 @@
+package measure
+
+import (
+       "fmt"
+       "runtime/debug"
+       "strings"
+       "sync"
+
+       log "github.com/sirupsen/logrus"
+)
+
+const (
+       logModule = "measure"
+)
+
+var store sync.Map
+
+// Start trigger record of stack trace run time record as a graph view
+func Start() {
+       routineID, stacks, err := traceStacks()
+       if err != nil {
+               log.WithFields(log.Fields{"module": logModule, "err": err}).Error("fail on measure get stacks")
+               return
+       }
+
+       data, ok := store.Load(routineID)
+       if !ok {
+               store.Store(routineID, NewTimer(stacks[0]))
+               return
+       }
+
+       if err := data.(*Timer).StartTimer(stacks); err != nil {
+               log.WithFields(log.Fields{"module": logModule, "err": err, "routine": routineID, "stack": stacks}).Error("fail on start timer")
+       }
+}
+
+// End end the stack trace run time
+func End() {
+       routineID, stacks, err := traceStacks()
+       if err != nil {
+               log.WithFields(log.Fields{"module": logModule, "err": err}).Error("fail on measure get stacks")
+               return
+       }
+
+       data, ok := store.Load(routineID)
+       if !ok {
+               log.WithFields(log.Fields{"module": logModule, "err": err}).Error("fail on measure timer by routine ID")
+               return
+       }
+
+       rootTimer := data.(*Timer)
+       if err := rootTimer.EndTimer(stacks); err != nil {
+               log.WithFields(log.Fields{"module": logModule, "err": err, "routine": routineID, "stack": stacks}).Error("fail on end timer")
+       }
+
+       if rootTimer.IsEnd() {
+               log.WithField("module", logModule).Info(rootTimer.String())
+               store.Delete(routineID)
+       }
+}
+
+func traceStacks() (string, []string, error) {
+       stacks := []string{}
+       for _, stack := range strings.Split(string(debug.Stack()), "\n") {
+               // skip the file path stack
+               if strings.HasPrefix(stack, "   ") {
+                       continue
+               }
+
+               // delete the func memory address stuff
+               if subPos := strings.LastIndexAny(stack, "("); subPos > 0 {
+                       stacks = append(stacks, stack[:subPos])
+               } else {
+                       stacks = append(stacks, stack)
+               }
+       }
+
+       if len(stacks) < 4 {
+               return "", nil, fmt.Errorf("fail to decode stack")
+       }
+
+       return stacks[0], stacks[4:], nil
+}
diff --git a/toolbar/measure/timer.go b/toolbar/measure/timer.go
new file mode 100644 (file)
index 0000000..801a4a2
--- /dev/null
@@ -0,0 +1,129 @@
+package measure
+
+import (
+       "fmt"
+       "strings"
+       "time"
+)
+
+const (
+       summaryPrefix = "|--"
+)
+
+// Timer is created for analysis of the function performance
+type Timer struct {
+       name  string
+       start *time.Time
+       total time.Duration
+
+       subtimerMap map[string]*Timer
+}
+
+// NewTimer create a new timer, only use for root timer only
+func NewTimer(name string) *Timer {
+       now := time.Now()
+       return &Timer{name: name, start: &now, subtimerMap: map[string]*Timer{}}
+}
+
+// StartTimer start track time for sub func
+func (t *Timer) StartTimer(stacks []string) error {
+       stacks, err := t.locateStack(stacks)
+       if err != nil {
+               return nil
+       }
+
+       return t.startSubtimer(stacks)
+}
+
+// String implement the print interface
+func (t *Timer) String() string {
+       return t.summary(0, t.total)
+}
+
+// EndTimer always run on end of the func
+func (t *Timer) EndTimer(stacks []string) error {
+       stacks, err := t.locateStack(stacks)
+       if err != nil {
+               return err
+       }
+
+       return t.endSubtimer(stacks)
+}
+
+// IsEnd check wheather the ticker is close
+func (t *Timer) IsEnd() bool {
+       return t.start == nil
+}
+
+func (t *Timer) startSubtimer(stacks []string) error {
+       if len(stacks) == 0 {
+               if !t.IsEnd() {
+                       return fmt.Errorf("try to start an unclose timer")
+               }
+
+               now := time.Now()
+               t.start = &now
+               return nil
+       }
+
+       nextStack := stacks[len(stacks)-1]
+       if _, ok := t.subtimerMap[nextStack]; !ok {
+               t.subtimerMap[nextStack] = &Timer{name: nextStack, subtimerMap: map[string]*Timer{}}
+       }
+
+       return t.subtimerMap[nextStack].startSubtimer(stacks[:len(stacks)-1])
+}
+
+func (t *Timer) endSubtimer(stacks []string) error {
+       if len(stacks) == 0 {
+               if t.IsEnd() {
+                       return fmt.Errorf("timer didn't start")
+               }
+
+               t.total += time.Now().Sub(*t.start)
+               t.start = nil
+               return nil
+       }
+
+       subtimer, ok := t.subtimerMap[stacks[len(stacks)-1]]
+       if !ok {
+               return fmt.Errorf("endSubtimer didn't find sub timer")
+       }
+
+       return subtimer.endSubtimer(stacks[:len(stacks)-1])
+}
+
+// locateStack is using to exclude dust stacks
+func (t *Timer) locateStack(stacks []string) ([]string, error) {
+       for i := len(stacks) - 1; i >= 0; i-- {
+               if stacks[i] == t.name {
+                       return stacks[:i], nil
+               }
+       }
+
+       return nil, fmt.Errorf("locateStack didn't match the expect stack")
+}
+
+// summary will convert the time spend graph to tree string
+func (t *Timer) summary(depth int, parentDuration time.Duration) string {
+       result := strings.Repeat("  ", depth) + summaryPrefix
+       result += t.name + ": "
+       if !t.IsEnd() {
+               return result + "<timer didn't ends>\n"
+       }
+
+       result += fmt.Sprintf("%s (%.2f)\n", t.total.String(), float64(t.total.Nanoseconds())/float64(parentDuration.Nanoseconds())*100)
+
+       // handle the case that skip middle level time measure case
+       nextDepth, total := depth+1, t.total
+       if t.total == 0 {
+               result = ""
+               nextDepth, total = depth, parentDuration
+       }
+
+       for _, sub := range t.subtimerMap {
+               result += sub.summary(nextDepth, total)
+       }
+
+       return result
+}
diff --git a/toolbar/osssync/Dockerfile b/toolbar/osssync/Dockerfile
new file mode 100644 (file)
index 0000000..05efd28
--- /dev/null
@@ -0,0 +1,13 @@
+# Build Vapor in a stock Go builder container
+FROM golang:1.12-alpine as builder
+
+RUN apk add --no-cache make git
+
+ADD . /go/src/github.com/bytom/vapor
+RUN cd /go/src/github.com/bytom/vapor/toolbar/osssync && go build -o cmd/osssync cmd/main.go
+
+# Pull Vapor into a second stage deploy alpine container
+FROM alpine:latest
+
+RUN apk add --no-cache ca-certificates
+COPY --from=builder /go/src/github.com/bytom/vapor/toolbar/osssync/cmd/osssync /usr/local/bin/
\ No newline at end of file
diff --git a/toolbar/osssync/README.md b/toolbar/osssync/README.md
new file mode 100644 (file)
index 0000000..2770e6d
--- /dev/null
@@ -0,0 +1,43 @@
+Osssync is a tool for synchronizing blocks data to OSS, and get blocks data from OSS before start the Vapor node. 
+
+# Sample usage
+## Upload
+Upload blocks to OSS. 
+
+### Build the image
+
+```bash
+$ docker build -t osssync -f toolbar/osssync/Dockerfile .
+```
+
+### Run in Docker
+```bash
+$ docker run -d --name osssync -v <config.json-path-on-host>:/config.json osssync:latest osssync /config.json
+```
+
+config.json file: 
+```json
+{
+  "oss_config": {
+    "login": {
+      "endpoint": "",
+      "access_key_id": "",
+      "access_key_secret": ""
+    },
+    "bucket": "",
+    "directory": "vapor/"
+  },
+  "vapor_url": "http://localhost:9889"
+}
+```
+
+## Download
+Download blocks from OSS before starting a node:  
+
+[Usage of Vapor](https://github.com/Bytom/vapor/blob/master/README.md)  
+
+### Start node
+Run vapor with flag `oss.url`
+```bash
+$ vapord node --home <vapor-data-path> --oss.url <oss-url>
+```
diff --git a/toolbar/osssync/cmd/main.go b/toolbar/osssync/cmd/main.go
new file mode 100644 (file)
index 0000000..04573d4
--- /dev/null
@@ -0,0 +1,13 @@
+package main
+
+import (
+       "fmt"
+
+       "github.com/bytom/vapor/toolbar/osssync/upload"
+)
+
+func main() {
+       if err := upload.Run(); err != nil {
+               fmt.Println(err)
+       }
+}
diff --git a/toolbar/osssync/download/download.go b/toolbar/osssync/download/download.go
new file mode 100644 (file)
index 0000000..861452f
--- /dev/null
@@ -0,0 +1,165 @@
+package download
+
+import (
+       "io"
+       "strconv"
+
+       "github.com/bytom/vapor/errors"
+       "github.com/bytom/vapor/node"
+       "github.com/bytom/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/toolbar/osssync/util"
+)
+
+const LOCALDIR = "./blocks/" // Local directory to store temp blocks files
+
+// Run synchronize download from OSS to local node
+func Run(node *node.Node, ossEndpoint string) error {
+       if ossEndpoint == "" {
+               return errors.New("OSS Endpoint is empty")
+       }
+
+       downloadKeeper, err := NewDownloadKeeper(node, ossEndpoint)
+       if err != nil {
+               return err
+       }
+
+       if err = downloadKeeper.Download(); err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// DownloadKeeper the struct for download
+type DownloadKeeper struct {
+       Node     *node.Node
+       OssUrl   string
+       FileUtil *util.FileUtil
+}
+
+// NewDownloadKeeper return one new instance of DownloadKeeper
+func NewDownloadKeeper(node *node.Node, OssUrl string) (*DownloadKeeper, error) {
+       return &DownloadKeeper{
+               Node:     node,
+               OssUrl:   OssUrl,
+               FileUtil: util.NewFileUtil(LOCALDIR),
+       }, nil
+}
+
+// Download get blocks from OSS and update the node
+func (d *DownloadKeeper) Download() error {
+       if err := d.FileUtil.BlockDirInitial(); err != nil {
+               return err
+       }
+
+       syncStart := d.Node.GetChain().BestBlockHeight() + 1 // block height which the synchronization start from
+
+       infoJson, err := d.GetInfoJson()
+       if err != nil {
+               return err
+       }
+
+       latestUp := infoJson.LatestBlockHeight // Latest uploaded block height on OSS
+       intervals := infoJson.Interval         // Interval array
+       if latestUp == 0 || latestUp < syncStart {
+               return errors.New("No new blocks on OSS.")
+       }
+
+       var pos1, pos2 int // syncStart interval, latestUp interval
+       // Find pos2
+       for pos2 = len(intervals) - 1; latestUp < intervals[pos2].StartBlockHeight; pos2-- {
+       }
+       // Find pos1
+       if syncStart == 0 {
+               pos1 = 0
+       } else {
+               for pos1 = pos2; syncStart < intervals[pos1].StartBlockHeight; pos1-- {
+               }
+       }
+
+       // Download Whole Interval
+       for pos1 < pos2 {
+               if err = d.DownloadFiles(syncStart, intervals[pos1].EndBlockHeight, intervals[pos1]); err != nil {
+                       return err
+               }
+               syncStart = intervals[pos1].EndBlockHeight + 1
+               pos1++
+       }
+       // Download the last Interval
+       if pos1 == pos2 {
+               if err = d.DownloadFiles(syncStart, latestUp, intervals[pos2]); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// DownloadFiles get block files from OSS, and update the node
+func (d *DownloadKeeper) DownloadFiles(start, end uint64, interval *util.Interval) error {
+       size := interval.GzSize
+       for {
+               if start > end {
+                       break
+               }
+
+               intervalStart := interval.StartBlockHeight
+               startInFile := start - intervalStart
+               n := startInFile / size
+               filenameNum := n*size + intervalStart
+
+               filename := strconv.FormatUint(filenameNum, 10)
+               filenameJson := filename + ".json"
+               filenameGzip := filenameJson + ".gz"
+
+               for err := io.ErrUnexpectedEOF; err == io.ErrUnexpectedEOF; {
+                       if err = d.GetObjectToFile(filenameGzip); err != nil {
+                               return err
+                       }
+
+                       if err = d.FileUtil.GzipDecode(filename); err != nil && err != io.ErrUnexpectedEOF {
+                               return err
+                       }
+               }
+
+               if err := d.FileUtil.RemoveLocal(filenameGzip); err != nil {
+                       return err
+               }
+
+               blocksJson, err := d.FileUtil.GetJson(filenameJson)
+               if err != nil {
+                       return err
+               }
+
+               blocks := []*types.Block{}
+               if err = util.Json2Struct(blocksJson, &blocks); err != nil {
+                       return err
+               }
+
+               latestDown := d.Node.GetChain().BestBlockHeight()
+               if latestDown+1 > start {
+                       blocks = blocks[startInFile:] // start from latestDown+1
+               } else if latestDown+1 < start {
+                       return errors.New("Wrong interval")
+               }
+               if err = d.SyncToNode(blocks); err != nil {
+                       return err
+               }
+
+               if err = d.FileUtil.RemoveLocal(filenameJson); err != nil {
+                       return err
+               }
+
+               start = filenameNum + size
+       }
+       return nil
+}
+
+// SyncToNode synchronize blocks to local node
+func (d *DownloadKeeper) SyncToNode(blocks []*types.Block) error {
+       for i := 0; i < len(blocks); i++ {
+               if _, err := d.Node.GetChain().ProcessBlock(blocks[i]); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
diff --git a/toolbar/osssync/download/oss.go b/toolbar/osssync/download/oss.go
new file mode 100644 (file)
index 0000000..ec25e62
--- /dev/null
@@ -0,0 +1,48 @@
+package download
+
+import (
+       "io"
+       "net/http"
+       "os"
+
+       "github.com/bytom/vapor/toolbar/osssync/util"
+)
+
+// GetObject download the file object from OSS
+func (d *DownloadKeeper) GetObject(filename string) (*io.ReadCloser, error) {
+       url := d.OssUrl + filename
+       res, err := http.Get(url)
+       if err != nil {
+               return nil, err
+       }
+
+       return &res.Body, nil
+}
+
+// GetObjectToFile download the file object from OSS to local
+func (d *DownloadKeeper) GetObjectToFile(filename string) error {
+       f, err := os.Create(d.FileUtil.LocalDir + filename)
+       if err != nil {
+               return err
+       }
+
+       body, err := d.GetObject(filename)
+       if err != nil {
+               return err
+       }
+
+       defer (*body).Close()
+
+       io.Copy(f, *body)
+       return nil
+}
+
+// GetInfoJson Download info.json
+func (d *DownloadKeeper) GetInfoJson() (*util.Info, error) {
+       body, err := d.GetObject("info.json")
+       if err != nil {
+               return nil, err
+       }
+
+       return util.GetInfoJson(*body)
+}
diff --git a/toolbar/osssync/upload/config.go b/toolbar/osssync/upload/config.go
new file mode 100644 (file)
index 0000000..f36bbea
--- /dev/null
@@ -0,0 +1,47 @@
+package upload
+
+import (
+       "encoding/json"
+       "os"
+
+       "github.com/bytom/vapor/errors"
+)
+
+// Config represent root of config
+type Config struct {
+       OssConfig *OssConfig `json:"oss_config"`
+       VaporURL  string     `json:"vapor_url"`
+}
+
+// Oss logs cfg
+type Login struct {
+       Endpoint        string `json:"endpoint"`
+       AccessKeyID     string `json:"access_key_id"`
+       AccessKeySecret string `json:"access_key_secret"`
+}
+
+// Oss cfg
+type OssConfig struct {
+       Login     *Login `json:"login"`
+       Bucket    string `json:"bucket"`
+       Directory string `json:"directory"`
+}
+
+// LoadConfig read path file to the config object for Upload from Vapor to OSS
+func LoadConfig(config interface{}) error {
+       if len(os.Args) <= 1 {
+               return errors.New("Please setup the config file path as Args[1]")
+       }
+       return LoadConfigByPath(os.Args[1], config)
+}
+
+// LoadConfigByPath read path file to the config object
+func LoadConfigByPath(path string, config interface{}) error {
+       configFile, err := os.Open(path)
+       if err != nil {
+               return errors.Wrap(err, "fail to open config file")
+       }
+
+       defer configFile.Close()
+       return json.NewDecoder(configFile).Decode(config)
+}
diff --git a/toolbar/osssync/upload/oss.go b/toolbar/osssync/upload/oss.go
new file mode 100644 (file)
index 0000000..512ad7b
--- /dev/null
@@ -0,0 +1,81 @@
+package upload
+
+import (
+       "bytes"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+       "github.com/bytom/vapor/errors"
+       "github.com/bytom/vapor/toolbar/osssync/util"
+)
+
+// PutObjByteArr upload Byte Array object
+func (u *UploadKeeper) PutObjByteArr(objectName string, objectValue []byte) error {
+       objectAcl := oss.ObjectACL(oss.ACLPublicRead)
+       return u.OssBucket.PutObject(objectName, bytes.NewReader(objectValue), objectAcl)
+}
+
+// GetInfoJson Download info.json
+func (u *UploadKeeper) GetInfoJson() (*util.Info, error) {
+       body, err := u.OssBucket.GetObject(u.OssDir + "info.json")
+       if err != nil {
+               return nil, err
+       }
+
+       return util.GetInfoJson(body)
+}
+
+// Upload info.json
+func (u *UploadKeeper) PutInfoJson(infoData *util.Info) error {
+       jsonData, err := util.Struct2Json(infoData)
+       if err != nil {
+               return err
+       }
+
+       // Upload
+       return u.PutObjByteArr(u.OssDir+"info.json", jsonData)
+}
+
+// SetLatestBlockHeight set new latest blockHeight on OSS
+func (u *UploadKeeper) SetLatestBlockHeight(newLatestBlockHeight uint64) error {
+       info, err := u.GetInfoJson()
+       if err != nil {
+               return err
+       }
+
+       info.LatestBlockHeight = newLatestBlockHeight
+       return u.PutInfoJson(info)
+}
+
+// AddInterval if "info.json" exists on OSS, add Interval to the end; if not exist, create "info.json" with Interval
+func (u *UploadKeeper) AddInterval(end, gzSize uint64) error {
+       isJsonExist, err := u.OssBucket.IsObjectExist(u.OssDir + "info.json")
+       if err != nil {
+               return err
+       }
+
+       var info *util.Info
+       if isJsonExist {
+               // Download info.json
+               info, err = u.GetInfoJson()
+               if err != nil {
+                       return err
+               }
+
+               // Add Interval
+               prevInvl := info.Interval[len(info.Interval)-1]
+               if prevInvl.EndBlockHeight >= end {
+                       return errors.New("New interval is included in previous intervals.")
+               }
+
+               if (end-prevInvl.EndBlockHeight)%gzSize != 0 {
+                       return errors.New("New interval is invalid.")
+               }
+
+               newInvl := util.NewInterval(prevInvl.EndBlockHeight+1, end, gzSize)
+               info.Interval = append(info.Interval, newInvl)
+       } else {
+               info = util.NewInfo(end, gzSize)
+       }
+       return u.PutInfoJson(info)
+}
diff --git a/toolbar/osssync/upload/upload.go b/toolbar/osssync/upload/upload.go
new file mode 100644 (file)
index 0000000..2d23834
--- /dev/null
@@ -0,0 +1,203 @@
+package upload
+
+import (
+       "strconv"
+       "time"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+       log "github.com/sirupsen/logrus"
+
+       "github.com/bytom/vapor/errors"
+       "github.com/bytom/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/toolbar/apinode"
+       "github.com/bytom/vapor/toolbar/osssync/util"
+)
+
+const LOCALDIR = "./blocks/" // Local directory to store temp blocks files
+
+// Run synchronize upload blocks from vapor to OSS
+func Run() error {
+       uploadKeeper, err := NewUploadKeeper()
+       if err != nil {
+               return err
+       }
+
+       uploadKeeper.Run()
+       return nil
+}
+
+// AddInterval if "info.json" exists on OSS, add Interval to the end; if not exist, create "info.json" with Interval
+func AddInterval(end, gzSize uint64) error {
+       uploadKeeper, err := NewUploadKeeper()
+       if err != nil {
+               return err
+       }
+
+       return uploadKeeper.AddInterval(end, gzSize)
+}
+
+// UploadKeeper the struct for upload
+type UploadKeeper struct {
+       Node      *apinode.Node
+       OssClient *oss.Client
+       OssBucket *oss.Bucket
+       OssDir    string
+       FileUtil  *util.FileUtil
+}
+
+// NewUploadKeeper return one new instance of UploadKeeper
+func NewUploadKeeper() (*UploadKeeper, error) {
+       cfg := &Config{}
+       if err := LoadConfig(&cfg); err != nil {
+               return nil, err
+       }
+
+       ossClient, err := oss.New(cfg.OssConfig.Login.Endpoint, cfg.OssConfig.Login.AccessKeyID, cfg.OssConfig.Login.AccessKeySecret)
+       if err != nil {
+               return nil, err
+       }
+
+       ossBucket, err := ossClient.Bucket(cfg.OssConfig.Bucket)
+       if err != nil {
+               return nil, err
+       }
+
+       return &UploadKeeper{
+               Node:      apinode.NewNode(cfg.VaporURL),
+               OssClient: ossClient,
+               OssBucket: ossBucket,
+               OssDir:    cfg.OssConfig.Directory,
+               FileUtil:  util.NewFileUtil(LOCALDIR),
+       }, nil
+}
+
+// Run synchronize upload blocks from vapor to OSS
+func (u *UploadKeeper) Run() {
+       ticker := time.NewTicker(time.Minute)
+       defer ticker.Stop()
+
+       for ; true; <-ticker.C {
+               if err := u.Upload(); err != nil {
+                       log.WithField("error", err).Errorln("blockKeeper fail")
+               }
+       }
+}
+
+// Upload find and upload blocks
+func (u *UploadKeeper) Upload() error {
+       if err := u.FileUtil.BlockDirInitial(); err != nil {
+               return err
+       }
+
+       currBlockHeight, err := u.Node.GetBlockCount() // Current block height on vapor
+       if err != nil {
+               return err
+       }
+
+       if currBlockHeight == 0 {
+               return errors.New("Current block height is 0.")
+       }
+
+       infoJson, err := u.GetInfoJson()
+       if err != nil {
+               return err
+       }
+
+       latestUp := infoJson.LatestBlockHeight // Latest uploaded block height
+       intervals := infoJson.Interval         // Interval array
+
+       var pos1, pos2 int // currBlockHeight interval, latestUp interval
+       // Find pos1
+       for pos1 = len(intervals) - 1; currBlockHeight < intervals[pos1].StartBlockHeight; pos1-- {
+       }
+       // Current Block Height is out of the range given by info.json
+       if currBlockHeight > intervals[pos1].EndBlockHeight {
+               currBlockHeight = intervals[pos1].EndBlockHeight // Upload the part which contained by info.json
+       }
+       // Find pos2
+       if latestUp == 0 {
+               pos2 = 0
+       } else {
+               for pos2 = pos1; latestUp < intervals[pos2].StartBlockHeight; pos2-- {
+               }
+       }
+
+       // Upload Whole Interval
+       for latestUp+1 < intervals[pos1].StartBlockHeight {
+               if err = u.UploadFiles(latestUp+1, intervals[pos2].EndBlockHeight, intervals[pos2].GzSize); err != nil {
+                       return err
+               }
+
+               latestUp = intervals[pos2].EndBlockHeight
+               pos2++
+       }
+
+       // Upload the last Interval
+       newLatestUp := currBlockHeight - ((currBlockHeight - intervals[pos1].StartBlockHeight + 1) % intervals[pos1].GzSize)
+       if latestUp < newLatestUp {
+               if err = u.UploadFiles(latestUp+1, newLatestUp, intervals[pos1].GzSize); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// UploadFiles get block from vapor and upload files to OSS
+func (u *UploadKeeper) UploadFiles(start, end, size uint64) error {
+       for {
+               if start > end {
+                       break
+               }
+               blocks, err := u.GetBlockArray(start, size)
+               if err != nil {
+                       return err
+               }
+
+               filename := strconv.FormatUint(start, 10)
+               filenameJson := filename + ".json"
+               filenameGzip := filenameJson + ".gz"
+
+               if _, err = u.FileUtil.SaveBlockFile(filename, blocks); err != nil {
+                       return err
+               }
+
+               if err = u.FileUtil.GzipCompress(filename); err != nil {
+                       return err
+               }
+
+               if err = u.FileUtil.RemoveLocal(filenameJson); err != nil {
+                       return err
+               }
+
+               if err = u.OssBucket.PutObjectFromFile(u.OssDir+filenameGzip, u.FileUtil.LocalDir+filenameGzip); err != nil {
+                       return err
+               }
+
+               if err = u.SetLatestBlockHeight(start + size - 1); err != nil {
+                       return err
+               }
+
+               if err = u.FileUtil.RemoveLocal(filenameGzip); err != nil {
+                       return err
+               }
+
+               start += size
+       }
+       return nil
+}
+
+// GetBlockArray return the RawBlockArray by BlockHeight from start to start+length-1
+func (u *UploadKeeper) GetBlockArray(start, length uint64) ([]*types.Block, error) {
+       blockHeight := start
+       data := []*types.Block{}
+       for i := uint64(0); i < length; i++ {
+               resp, err := u.Node.GetBlockByHeight(blockHeight)
+               if err != nil {
+                       return nil, err
+               }
+
+               data = append(data, resp)
+               blockHeight++
+       }
+       return data, nil
+}
diff --git a/toolbar/osssync/util/file.go b/toolbar/osssync/util/file.go
new file mode 100644 (file)
index 0000000..f5e3fb9
--- /dev/null
@@ -0,0 +1,49 @@
+package util
+
+import (
+       "os"
+)
+
+// FileUtil is a struct of File utility
+type FileUtil struct {
+       LocalDir string
+}
+
+// IsExists if file or directory exist
+func IsExists(path string) bool {
+       if _, err := os.Stat(path); err != nil && !os.IsExist(err) {
+               return false
+       }
+
+       return true
+}
+
+// PathExists return if path exists
+func PathExists(path string) (bool, error) {
+       if _, err := os.Stat(path); os.IsNotExist(err) {
+               return false, nil
+       } else {
+               return err == nil, err
+       }
+}
+
+// RemoveLocal deletes file
+func (f *FileUtil) RemoveLocal(filename string) error {
+       return os.Remove(f.LocalDir + filename)
+}
+
+// BlockDirInitial initializes the blocks directory
+func (f *FileUtil) BlockDirInitial() error {
+       ifPathExist, err := PathExists(f.LocalDir)
+       if err != nil {
+               return err
+       }
+
+       if ifPathExist {
+               if err = os.RemoveAll(f.LocalDir); err != nil {
+                       return err
+               }
+       }
+
+       return os.Mkdir(f.LocalDir, 0755)
+}
diff --git a/toolbar/osssync/util/gzip.go b/toolbar/osssync/util/gzip.go
new file mode 100644 (file)
index 0000000..1fe23c7
--- /dev/null
@@ -0,0 +1,69 @@
+package util
+
+import (
+       "compress/gzip"
+       "io/ioutil"
+       "os"
+)
+
+// GzipCompress Encode file to Gzip and save to the same directory
+func (f *FileUtil) GzipCompress(fileName string) error {
+       fw, err := os.Create(f.LocalDir + fileName + ".json.gz")
+       if err != nil {
+               return err
+       }
+
+       defer fw.Close()
+
+       gw := gzip.NewWriter(fw)
+       defer gw.Close()
+
+       fr, err := os.Open(f.LocalDir + fileName + ".json")
+       if err != nil {
+               return err
+       }
+
+       defer fr.Close()
+
+       fi, err := fr.Stat()
+       if err != nil {
+               return err
+       }
+
+       gw.Header.Name = fi.Name()
+
+       buf := make([]byte, fi.Size())
+       if _, err = fr.Read(buf); err != nil {
+               return err
+       }
+
+       if _, err = gw.Write(buf); err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// GzipDecode Decode Gzip file and save to the same directory
+func (f *FileUtil) GzipDecode(fileName string) error {
+       fr, err := os.Open(f.LocalDir + fileName + ".json.gz")
+       if err != nil {
+               return err
+       }
+
+       defer fr.Close()
+
+       reader, err := gzip.NewReader(fr)
+       if err != nil {
+               return err
+       }
+
+       defer reader.Close()
+
+       json, err := ioutil.ReadAll(reader)
+       if err != nil {
+               return err
+       }
+
+       return ioutil.WriteFile(f.LocalDir+fileName+".json", json, 0644)
+}
diff --git a/toolbar/osssync/util/infofile.go b/toolbar/osssync/util/infofile.go
new file mode 100644 (file)
index 0000000..82f15d2
--- /dev/null
@@ -0,0 +1,52 @@
+package util
+
+import (
+       "io"
+       "io/ioutil"
+)
+
+// Interval determines the number of blocks in a Gzip file in the Interval of blockHeight
+// StartBlockHeight is the start of the Interval
+// EndBlockHeight: the end of the Interval
+// GzSize is the number of blocks store in a Gzip file
+type Interval struct {
+       StartBlockHeight uint64
+       EndBlockHeight   uint64
+       GzSize           uint64
+}
+
+// NewInterval creates a new Interval from info.json
+func NewInterval(start, end, gzSize uint64) *Interval {
+       return &Interval{
+               StartBlockHeight: start,
+               EndBlockHeight:   end,
+               GzSize:           gzSize,
+       }
+}
+
+// Info is a struct for info.json
+type Info struct {
+       LatestBlockHeight uint64
+       Interval          []*Interval
+}
+
+// NewInfo creates a new Info for info.json
+func NewInfo(end, gzSize uint64) *Info {
+       newInvl := NewInterval(1, end, gzSize)
+       var arr []*Interval
+       arr = append(arr, newInvl)
+       return &Info{0, arr}
+}
+
+// GetInfoJson from stream
+func GetInfoJson(body io.ReadCloser) (*Info, error) {
+       defer body.Close()
+
+       data, err := ioutil.ReadAll(body)
+       if err != nil {
+               return nil, err
+       }
+
+       info := new(Info)
+       return info, Json2Struct(data, &info)
+}
diff --git a/toolbar/osssync/util/json.go b/toolbar/osssync/util/json.go
new file mode 100644 (file)
index 0000000..c3795cc
--- /dev/null
@@ -0,0 +1,42 @@
+package util
+
+import (
+       "encoding/json"
+       "io/ioutil"
+)
+
+// NewFileUtil creates new file util
+func NewFileUtil(localDir string) *FileUtil {
+       return &FileUtil{localDir}
+}
+
+// SaveBlockFile saves block file
+func (f *FileUtil) SaveBlockFile(filename string, data interface{}) (bool, error) {
+       filename = f.LocalDir + filename + ".json"
+       saveData, err := json.Marshal(data)
+       if err != nil {
+               return false, err
+       }
+
+       if err = ioutil.WriteFile(filename, saveData, 0644); err != nil {
+               return false, err
+       }
+
+       return true, nil
+}
+
+// GetJson read json file
+func (f *FileUtil) GetJson(filename string) (json.RawMessage, error) {
+       filename = f.LocalDir + filename
+       return ioutil.ReadFile(filename)
+}
+
+// Json2Struct transform json to struct
+func Json2Struct(data json.RawMessage, resp interface{}) error {
+       return json.Unmarshal(data, &resp)
+}
+
+// Struct2Json transform struct to json
+func Struct2Json(theStruct interface{}) (json.RawMessage, error) {
+       return json.Marshal(theStruct)
+}
index a778738..1c60f8b 100644 (file)
@@ -21,7 +21,10 @@ var (
        errNotRewardTx    = errors.New("No reward transaction")
 )
 
-const standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
+const (
+       standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
+       standbyNodeNum                      = 32
+)
 
 type voteResult struct {
        VoteAddress string
@@ -115,10 +118,8 @@ func (s *SettlementReward) getStandbyNodeReward(height uint64) (uint64, error) {
        }
 
        voteInfos = common.CalcStandByNodes(voteInfos)
-
-       totalVoteNum, xpubVoteNum := uint64(0), uint64(0)
+       xpubVoteNum := uint64(0)
        for _, voteInfo := range voteInfos {
-               totalVoteNum += voteInfo.VoteNum
                if s.rewardCfg.XPub == voteInfo.Vote {
                        xpubVoteNum = voteInfo.VoteNum
                }
@@ -128,12 +129,10 @@ func (s *SettlementReward) getStandbyNodeReward(height uint64) (uint64, error) {
                return 0, errNotStandbyNode
        }
 
-       amount := big.NewInt(0).SetUint64(standbyNodesRewardForConsensusCycle)
+       amount := big.NewInt(0).SetUint64(standbyNodesRewardForConsensusCycle / standbyNodeNum)
        rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
        amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
-       total := big.NewInt(0).SetUint64(totalVoteNum)
-       voteNum := big.NewInt(0).SetUint64(xpubVoteNum)
-       return amount.Mul(amount, voteNum).Div(amount, total).Uint64(), nil
+       return amount.Uint64(), nil
 }
 
 func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) {
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/.travis.yml b/vendor/github.com/aliyun/aliyun-oss-go-sdk/.travis.yml
new file mode 100644 (file)
index 0000000..516f1a2
--- /dev/null
@@ -0,0 +1,45 @@
+language: go
+go:
+- 1.5
+- 1.6
+- 1.7
+- 1.8
+install:
+- go get golang.org/x/tools/cmd/cover
+- go get github.com/mattn/goveralls
+- go get gopkg.in/check.v1
+- go get github.com/satori/go.uuid
+- go get github.com/baiyubin/aliyun-sts-go-sdk/sts
+- if [[ $TRAVIS_GO_VERSION = '1.7' || $TRAVIS_GO_VERSION > '1.7' ]]; then go get golang.org/x/time/rate
+  ; fi
+script:
+- if [[ ! -n "$OSS_TEST_ACCESS_KEY_ID" ]]; then exit 0
+  ; fi
+  
+#- cd oss
+#- travis_wait 50 go test -v -covermode=count -coverprofile=coverage.out -timeout=50m
+#- "$HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci"
+
+env:
+  global:
+  - secure: ZCL5egxJmZA+o1ujsaJe//AIh1ag/exSUJ2FzoKPF3O9c84hMc2k5EYO2rGzTNn1ML6M89Mo5hAvHQhyJEHjRuMtjc1QrfxAaA3mqm4scGXIXesPMqYGuvuPSh++6/fkAwaVBAhrk5GaDG1/FuxHE5zusGx3SvGegnCwO7n/2YCfXco6DWgVCdrz4p1EpPkAM3JIdHFUzsDWiimVuiNAvJmAT8+IeOPTT+WgusCJj4ORS3X3LddTjttBP+hRrp/pGSoNqPMzfysWybtaL2SJ8URtvsxW0Mo5BwocHAxAhPP+M2OscQbDzthSAezCLngYvrfBplfIyWlahlgzNz/FjXz5pQwWdYVNoibyxLLMOH685n75LNONN/xVO/GFmVPx7DMGapkN5NzIWS62D4v8QrRkwtms42OUkyEUHjDh8Evui3K2MNJVXA3TI9zOAR+C0krD7OEyS37qrppodhRxJSqFUlgXnk//wLldMC7vleDd7L2UQSWjqyBHqFOgsVaiLU2KRTY3zvv7ke+dqb5VF31mH6qAr8lJTR9un8M1att0VwCEKxoIRT4cKJCpEtZd8ovXOVt1uE695ThVXE9I5e00GXdTzqXOuv6zT4hv/dgmbz9JN9MYeCwmokEoIUmJKNYERa/bNVVefdnJt7h+dm+KpyPAS+XvPLzjbnWdYNA=
+  - secure: GdrPX7nUoZhB1DYTVD6x/vgA7H9dOlQc4n7nCsqEDyif+Y1XdPT83Ic3gSOt+cfy0/Kjh0/TT5xmLqpSh7wr7eyTpBPZGjz4ZbxBOcSLTfrf/spacgzla9I1335CvaTmpvrnvGUlOuVS8rb3J/+19dHlN6dfxX+ucjdfShR504d2JEcCLpTc1CEXAl+HEt3hM9gztOX5ykxyrtibDr0OPkNF7QjZ485V6UJkfyVlBM6JL59ywgh2dhdZn6JwmexHjVPsw6V8Ka07GzbpOs1e5eis42RUJe8eSqRRToCcTUbA9HOgWXswuu5k7nAwErygX2ub3hZ+yIjc+9JLsiy6F69RaUPVFlxfw8s5NLeInTIt28+A6iaf3X2k4lOaVFytgTl7lkYGNWz4eV/vXf2H4wZmaZn5OI0WKd3WuEJ04rsm7xzx9rC8znnsI3R1BHfapU/y6z2QGjgJsHqZmgfvXNKgSOurM6O/nlDnEsYOwYLQLhpeXVFNmbo+M77HAVicKD4yL08+5uBZaeYYipzyC1O4DEHX5BNdl34NpNxUdMqUb4MfNEnYeqvmemvZkOO6BO+xucP1S2LSKNXfFxH5iVfKbz4+VJ/2kt5R77672lkG3bXPUJFk4t1CTHBOLizEJNTTD8uzRIsW7Io+XFk6oyoEqXF0sT6Lbp/4pUHJQQM=
+  - secure: DGIgOKinCvYcLdcaIOKcecidQe5q/K4aGAjTyl8/fCp3mRWwFTrlv5gPMy9sHQEsiRjzQehpubMO1d0XFVO+0LvqdGLnXyM6lSnMhN6voQMnF1GaIXmxxBfvP3BHwyN8kMyY+4oqgMROvpxDKvq0IH/GE8opWRJhQdNkRscbtfHUvnqSk62oNziqIBxXrYBIuezuNcgZkCwEoZQwPu674efIAwVr01BmRfd/8Q5W27dJbCJFF9ceyOQrsjG2QGmW6GWraaOpqYfQ82A5ROB2saVU8mEs/f+mGoJgOP2aH3ErFBkJWBCUNzajluAyGyU9VgHGRQ+GMbSr/HqRILd5aYpDCGA0oFer4/jP32CqeEt1Jdqs2lWar5mFX1sae6PIxFyl5lnENgjTfbOt4oiGGye6t0mgI4+psRdCCQV5FQ7WfobEZ2ryfQxbiVU1X219jMoHhHmFzC83e/T6V/mkHrh8OR67k9pieH+DqNGvWFtv7BBs/ihfoo2ONNgsHcofsPj85I6odWAhsBnsYm6FsR31N19nObnggeDyqiCyh5qUFvSGPkH4fXTKthKETIRdsdOEDOcbCD6kUpZqIWyuk6TKeOD8PxjgKzm3hZjlugU1x3amVv71EKjA5/FOVyIVuekLKoLn7pt+n3PVlT30IZfWorEfqjeVAKp2SglE8nA=
+  - secure: LnL0Hl9yZEie1aYngEO5QK332cn3W2i4f6R5+kxX3tncdqBDFhsp5tQfMvlKHIzFlK94DI/G0diAl8zJmYQfAYARe4uvW5FAINRCkOUz2jwIA/gtQDr+oONqHK0OLPWYuZ6KJM4t7dmuPUR2/frKe0/6r5XeFkeAz9l8r1Gw9o42jFDQPDkhBj7k5EkmB1DpuAY74vXy0tVBCJsd57/kuaRqbX4euhx3zFrDcr+xQEiDWHKzNHlJd6DZnruy0KDuWmIbUWhR2rd8YKAnzP7OHzpbTHsnbXvrVpaN1Mv8lXz2dpPTr7I2LMCrMEtfECu9U+LDIqhbHVMsp9rZ+fNQ048fREoj8HZrorIxmsRJzV0ZQzjdW9Q6EVaiYcLZPFOASsyuTNBbSJ2AIrE/izo4EUKme8BY+0mFzTJwMk7XwAtatItVhEUXb2wXWZm8GR8wIrNmbeSzle5NkwXpdpW3QzZ2EADL63/pP80aV1aMBmoAuLMIHxeHEnXOTAgQj6SOiloY+II/iJE4cE5vo9UNtZsqnJZqdd22s3kLdQV0kbFMWq8S3qmxtDFPeoZAy03xhTVnJUBkdjSL2UER5WAacZIr85M6Z2APc6dzMUlWEE+4QkkM1UAbwDBTXFrrmfDVYc0LrePRuoHQiOmSvTus5+WV9iIQF7rM4BcSLnOEW5U=
+  - secure: SgvbTYTbMEkmqDXP8MW6lbERkHUjBRwg477hUL11Ok1TiRdHCbEDrq3mfUP+Tl2sS1x5qQ2JFg2NyWS2ikCAd0zjO3QEfmhfQFRpmfgb5O67wY2oEAsbRDanjIXTwpDAZn87KFIPB6ohVsX4LEztfR8zqXKIfXrVFs6lyDHS1LIgbgQhJl+XfJRsfPlWRq7QydANTCY34raUXgXgBxtbv43b232LT8UusizUvZS4HJbrbo4oXhVfhkUH46B2o0ct2Xt6EIlxyOtxtZOmnai8O7kIFHoG+GcHxeZ7++X6FaHR3Cdv3rr7EEg3MwsOIZ6SmgeQ0gcs32RZf0giFMwo1LkgiB9KrJTBXkU0CSYysxfeLUCEd11q4h4lZyhxU8CMvgs+1m3s6A2/5uYDYSSqJuAHTHQgntMn7/baXKVXuWXrSSERxkUiqwlKjFFHz0kshj3ZXiTZ3EhjBZgXeeGzGEEbZBQCEJgXKUpl+C0D07PLKt6f2ya4TVTZ3WOjh7dKq14+0nC3w+5z0ZtGtv+IS1LFfajNs/LsT4fDmKsIoEQg2Kf5S//ckeuzaR4bkMGCm3qquJrNE6Uqq0MZzSUdUpnsILjfLiVlrtHk/9So7ulRc8XyhBIWDy9lTS7NxOfpw5cnVJRcjEwrZ4Q6iNwdsh4vPZLVgavwfFRW87DjF6A=
+  - secure: Jef94P0QfHuMT5GQNrzfMdtVUVvV9dEGsvOLFqPvIkPLJZWGqwaIFUG5t0mCPEgn8uct3XPOiIYivgxnOURa1JNegnTbjRLXBOAjhE5hw2x2IRWj2xT+ylYCNqh9jtIEJdAjUzJnXXj1iasZGCKa4DNwNLgsuQ8d5Yl3bY5l5YI4MtTsdzbBUT9WewDWgnO/MhZM52w18XLBx+Fsq80F0VwpzoStKRula8anOhL+Bvj1uAielMOUo3QcpYcV2XfTnM5n0ApwqUhmv/8YoJpHXjGTeKRM1Hem1jFtfWCjSRrlEKEFJALEJImf0iWbZN5Z0TOcfJqzPY09/8h60OOfi0TXcnwVnSX33Zp1oDLDlRnsN7HQg+yIub0N03OwHqmC2AO0ShkO/lBmEMsfqlEoc4o2GJ3YL+JpC5vPsy7fFMad+jNGXlg6jPAshvCJ2DfnmK1jYSSVdVNUUeP1Bk5rhQkFzFH3vgNgX3nFk0gEYrfDn3/Ea6tORybSJzaAkB9bU1n4U2e3OplvWr1Ll8O/t87ws8ctyY/Ah2hRmhSKEG9cdySnm7Uq8H7696MZEEw9aatj+bRJk5CbCVtSX8v49I3C0tERcUBO5M3U+/g0qeBW9hEhxnBeG3y253Bo1FhSxbaZhGwSGJ91htRXLlJlUs2QrOcSYMsCT6p35KdWaqA=
+  - secure: NMVS9EU+ahQXGiyTCHyZ44rf+8b3me3UXD1DozMm04lCvnWoBqJE4aXBGQsDAWuOL4NTTm0SaVu6sBY6ZTXOYYF59mwEbxt4qpmVjZ+vBrtMbMiqoxv145blquR9JKedkdP6IGSd7VSQwSba71f/RVv5VeGvxUSEhCwA04kKxToOPwmnORmT6qwb7PkPCMNHxz4VpsUIsKx8jRrY6Gmp6FvQJBHfKEHnDQohB1ReIYEYi39ijLvpbCZqrB5u1N9oF6WlpBiNIX3kQizn7ftUyewJgoZMnfpW/Lta6e91yzFInWg75bZdW3faa30Qy0yw0zlQIPLs89c8A/XH1fGVECH9At9VNmdYrb0fD9aWnH7zdX6Im+Bw7Ptph4x6tB7zPeFoZR5cVZT7L06/HbnW7NeQk4tg/N4I1tOaO7AQl+ofhCzesZ56bSxETiNFn9QiNwWFTzjlkG7jxN1iAAkdYsZEQHwtEK63R//NJtXpbbtNA831QqgDqBK+IxyKeLhmxmu17dWcUw9tm4jlZ7d6nPB9bzJcVM6K2uRJyW07SlBqd65WJTXPV1PFww8zh+chAC4ZkLDhupn+7ZSG2ylLYGgepmABoC/CXHkXEsNzdQ8wPX/pDIz2WNmwEXyC/Nv+WNpFS/tWIAryIPOLMuETIgbaOLbD5vZDSKxDZVGDvPE=
+  - secure: cNr4PiK6ZZADoRMacL4lvdMYWgM9H4lKN3u+nfMA/MrVrnSjeRonkO7RjMJWs9HicPanEks10R1w/T/6nWyFQV2CPkEBSNSLf3DAD+dlRekKfWogGXzYnvqeiki1HzsIPYTImiD5BtPn6SbJmO/ErJt3zuogspyBws/X7XfZ+u8FIpPsYEmvHslT2jARuput0yNfldUgxxyI0IvgkuCEcCTFwePspjbn6zR6Df67e+r5ibFqkdPYdXkQVpvfA90RPpfBUuuaEO7kkFlKbPK+Nl/jbUnjcfbe8zJRpSb8j/S2USAiBUjFsqsdvFqZ9WumjXJLrrNFt/UgIXaMyG3Y8xJl9kzCcx36wcNoaP2mx2qucYTdC0ey49g0uywvOVDdykmctQRF7uYQS+UkMqs5jRLgAjQ1/wJISrvtcpaK/4DyhLBUFrOf9chep2hzWBFaWPto1IUpWu9Sjtz5rtjsAm5LR7zvIxcorvRall5kRokAspHb9+TaQKSDOpYNF+PB77cb9H0OLZBLVPGo0WJHq5dv0NVZSH9JVq4AiJDmvMWI6weBis+tLbECMwbeTezo6hDOuII7VZt/KcHgzlt3KCDwv8krQq71q7ySDt7SxrvLeDjeiEFkjwA0lN7Cin1yqjON83LsIsWkKxbf+xNJRngSE4bPn95j3pHELdo3uqY=
+  - secure: iDkNjibPknbawHN+jobw1AEqhQDhqFvGPBUX7FkxlrIanNR71Tj8CPAFtDpJbYaBMdPt4IzOmD2JVo9w7E1TfNX4DsOpkb2MbOr55TxfXQ/+y7TBdJ9B/62BvhFWk8Hvq8TWVPTCgNIaVXNfqBAj6WQSX3AbUVHWSOM9Aey5joBZuhdWXSmKo46lLOradacDDPZYjrLEsMTS2CotzduKQ4C8dGCVcMEbBnS3O2WRj3oR0wiiP3X0jbnxJkOV2MCoadZxSu5B+gaaJ+Yv7EKT0vy6ASp6LYrWkjY0eKbTqy8NtCvCFlliND/iaq4LEv838hQfO/o0WeB2b7/2MH2EW1v8XLacV12ak5wJgb7b+L6fG+lMKMta5Re+vjdRYgoU5EVeWQNxrnX1chEdzFXb/q2+5DVp43qH5i1Tu4FR/kSBobQeSAbT7HTkWAVz3kg8HmubYZ3P0eXToZA/DlX0dphWxO9ShR3H+XTJhh3tSqzxMZxxhGqPcN4DPSfOTnJQ0v0NPz016lulCr9SuIOSM3f7HpeGXo5SeQbrY3yCnBG8Qxpx2kYaZZlT4J6fx3iFl77SY/lQu6H/Y8ZtufWEogPSkGEh+NLLWuwwBQFC3vH8l3J26vcqHZR3N9+GyqX13CSqWEUysMF4nBOi52ckhwJRF8hAeX+DIqxoLfjUkDc=
+  - secure: iZmWFdPBt9JCDJqWGeX0gznM22ulUrz15Z7YnDfWNhniMs759YWgIdt3MGr2blMmo5MpDZiticaBcE9aX3p7aniPZkbXcebswz5DwCKVRUf1dhtrzwbnDebSwKrlYG8SuGgMkKH/7F+sEkHtTy+SFDkFwEAwcGjPdPntEX15U6KPrlpD5AOvq/d83xNV6QYleJmkRjsTiUjgGzbYfm6Lu/bzBsckYjwWjcz5rxkeSIQHV8/nnw7xs0figTVcb0K5p7Rpvhg3TCAlSJcljg9IDOEuvILQGNDpdgzXajTTNOHYm9nOBrNQhTn35c8DRDl0uQFe+c6PBvBNhSvSKZ2t9REjok70gR/IKJNTuecS/rppYG8wfjlGg1axoJMZJG1OwOJxWiMHwa9WM/diJ2e5D/WTbQ1T9DLRp8Aco3LnWSfZ+tZH+4tAS9FE4oXEPLlHtRyRIV+MzPK3HinIXYdwGH7pDaAQDnfH562br3tl3CovjAPSrIlDgJh92qatLk+1fBQT/7paMNdelAWLjcujRRI9MWsKzi/aEUVbTfUOSCJH/vWQRJR9or2saOobHE+G4f2+k77r21YRztrZaJF6aiob/qf3altKW5rQRdj/7PgNtG3N/bwFOgRjpOQ8dgNn0J+xK4PQTtSVH7kD4aIcJma23erS+K29dcY/wk6fuBM=
+  - secure: IkTwVHWb8yGufpyBaQDL1WpTywnMb5FhhtXgP8ycxK9MIuKSfgxPlQm6Oel/8Xjy2HhfKTUX0TOwQ4yAuZtRuU/T5NdjGgBYdLjGWwd/Wgkxt7JkpkFd8lvMrh2moAgvf/Qfk0P73OdAyn2y2CZhm7F7GI3lD/K4NmbFnR5AkDGDBB7J81zc1eTGjMfYRELHMFLJTg8hKr64SljXuRBmyxAVImaBROXzml2pP3UywWKNsw0eEOaeJaVJFkStBh89IOYaznckdstdbTuMw9+hZywBTcQHqxaaI5stdtyKgJcyEcjdJOqNGwCf1Hqd18SH7aCfXsUUrwx62ZSheiQRapOz+XNMiKC+1nimNvaYIFEuPZUGjYfi2/c27Z6paBI9ylGpoUXSvCH2rmaCYUIJBRXVmqzvhi0am5FKBh9NovkKUE24B8rMhm1xV82HQLTCho6MnemXFfji2AXaJzYAa6lU31b8BAzQfy+Nn9lh8I/MMjd15Y+8nHFjKqeKZIBi5qO4B8AGNYwCBFhrIOF6uHGubOuvyKv8DSBxcaDhCoyOLa5QLT4vQzQCX17xUAFomXkX9mHbJA8kTxMaFOQPeVCHkfjZxuui57vbPt4CafGzbymi/SE03oNUheQbuHdqy+jkjj7XU6d4FdNYHxp4rvhtvjYrPJQ5gr6A3sdurd4=
+  - secure: WNjlMEt8xGRc6blhZF9HkyWEQukDzEwbk/hIljEz+K00W+ltDxPqFZ4fsgnjCPLS0fTL1zgtg3Rji6Mi8hSQd9Z5Yh98fCHN/gBfGxziKB0BOlVQDAl8yaUchiHZ6biM+HC2AocbZXlaNcSOHJy7vQk981y48PNPFadZgJZWWF4eFMkAn+74sPMW53V9J4J2ot3AZ7+ABg2vHTbqdA1s09/6fjlTcKVSxm7eDkkoOYycxrD5k2lm3N0I3YvkWum3ZMcCi1bWk4WXQSgy8q0dy7QBya7+BnRWh/EO2HNFAO4l/tqdev7GC3/tSgTbepNV5GUHazvsrANzZGnWP5kpjjis+Z4GiB3TMNLN1c+KeH2kbBkxi0vyDP5rzzI/rORRp5a3SBDAPkz0u/CHB9sltriErp86v9w6YxDRx0QcFheEXbcsk+3Vajy3+v1ly2PgMfLXcfojCfc27ymUCCymXMHrTG8DeUMnfIyUoP2xDx59tZH8rRlbUQmtlLhBYxPojsw5I2YhnMv4ThHZ5RhGSbNQuZ2yHsdQeGcTqSZ+bVT6xHbQbP4nk6ZtLHC6eq/pAQjFFUkZhfQLH8fes/2fVbdiRjYeX4P8tGnc2NB/fw3C77klizMYIGNHDAK8pKbfov5Q1zeg5/QB7weOvv9xpizUAjQsW5+jwQ1tkdxC0iU=
+  - secure: kllkCVsDmsu5Z4ZBMFN98VS5LNoyy0z8mpkreV1sAfPH8mmy/q1ZJAGkXLoXL5vFGOTBumMsDs6a7S74d1dXyBkQGYiegs2G5wqIvWb/hsq4dTudEMPqwWnVPCFs/YmtxtzjX788sS7ivkZsHA2pXh1w6glEZykXHJZgWWGSL6XFofR5Gmvpwc8SfQBP/iBi1xb0EA9YWUqjfW43FAuRMp3QDKQU2gcRoAr1EuSIvmMmarnK9CEMJunK/RY94OW1AwtUcJbUVLnK/PvHBIazY18BTez/o45gATH37eMk0G3JKxMP8yfk+wipAAVyZbloWeTdBW8ik1r/B9NWSI4ZXi/PysXMZzSkrede3Y0TE0TqW3mVkdtdcq4yFXD/B21dMmeNoBLxQGi290p/KNfnJLIcYB04CU+Ll9RoXWnVJH6RhxCK6JGEuiYqJKm/UZkCW5dXMhtFmjK3XH3iBRC9kw0jYI7fcTgstLzYzKzgAXBP7LSiiwxDUFsvq480US+lECGdDIJYL646H1kHogi2Cr3BBgXHvMoBbUFyLVXTHidzi4Vk6PFaT8fMXcJIO/j46WUcP5nyEh+7qiJmgqtZx6AbVIuank5vnDSKg9X5fsiww4cT6Xcf+E9rxcpiLhcfBjsbkUvKiVVcCNQHOIZxYLwEwelghz+VlWrJCFVPwT0=
+  - secure: g0Gm0G5oj892VmJeX8s2MMNttIR1GW9DX8+DYR2qXs5C8pjT8A30dYxCFiv4a1QHIrPEAhIGIosgtWpHXt8JwCDNLbPaHs6Jp9EkCB6doqLqQ9eOrvdxM67fyp8fJVcd4IH5JenG6FoaE7ofnu4QKoLo5w96Cd2UthwxrSCSQXWniutX8/N7XUt2cE74ISqQbJyslPWJ2UvKYL16HHtB8EzRmxD+EZgSYU/vtlds39Knrk85ogK5h6PKDmne8qoIH1nTPs8vzAu5uzeG6zai5m1qYETYbkiyJPTDs6sdy+DkYLLDaE5nLkNyfC/sHQvbPQ/H/CAW1OTZvSPsjxDN4hksovLaVuiH1DTHTM2+4fZsEab6fhx1itPOVijskoBqMzmksvah0nogIaw/qKmCtXGNBk7I30xeSju0W6c55miYUEEczF3eUn9oxHUSUDhQNs8UuosW/MJph7WTS2ESvmIjU7R6QuQjKmxTK7kZTaLAPpSwj/f77F7RgLUu3v6tO3Db1+i1OxdHvbaIVDiCNPB2J1LNmN4LFQstzJepYRadEUxYiExQzNulmC/FpkyPGwajec06z1r5NvTcWSOgxcTU/5OY6ggRCAsFoZTH4VzAst5nOBLRRGT+WAXpewGLhRlGLhev45eGgL2ctNVZY/6MA0ZBwnRPreczO7f3Nno=
+  - secure: iJ9fPWg8j+q19cUNqvwAbULZTnmK25wFuF5uBCxaSDuM1qMtbiq3M2l1Gvl0xZWdnq12PLMsUZtfLYNO1r4SRaarMPtnq4pK68DnuH49vkjpwGSy5bLIrcPQBZvzWKqzyG1R8+eOpg43BmXdau8Wo7pOSDw/8Bra7rtvA8Kjua/7MXaiZP08L71DtHaHFOoYlvgGHmuscdvtV3v4rNPntGR54ClzyNgnrRz6DP/NwEUF2a6EF2uhhbbe5XQvCNDTW0sqgxirZ4pDFPgywuWQaPcqqSXcwF78wrA0nLSzmQ1PsW9g/rUkDPbXE3uZuwje8QntNsSa8doTLw6Fizj7ZD8DuyvD8z3ljrLvhym+I00r8k6BpD5vlVhasUEEix/3+7lW4cpbE+ofwVz0Ge96y3Ei/uXkSI4kEhwPVfsU9sCRX/Kupdx9fLCQzm7F/BhkfHUCd403MjpbAtoHN3ACZfzC2QQ5fm14mxtLyMSawsClqwO7vVmc7+5p4f13TTkiUtWllKghMw8zTqxAZLwTiPFRKG6Y7MkX9tQUDBb9auQm/+7+pBFEUHibwoPvsSRwYGqdlZV7OXIFYPgt+WgUt9ZYblzSfZ8YLcGPfVTlGbLHkvW2SgIGL1gvHyXC2xd7kd7yec3M7AutDCiYg2kSzANVuJqGQCZSNr/6/S3QQrY=
+  - secure: IM9VEzf9MrtQnzK3VSD+YxmZWycxTgB5YbINDwE0LsAwWUWucDq95+2ZZRjKLrip5uYvoMohC5X2lxKuLhW8uDN3M6MxondHcCjgBCh8hWP7JociXWMxSIWA5ctU0oCgobm6rKvbarDuO8Bx+NE4QDDAWgjy9oOywE7mVi1G2//LY+2zC+iwXlkLs7VRdo9wtKgIG+FvswiJHhvFbU6GYxOeh1xclLnjwoWf66zzzbIW9Z4bFY0Zun/3u1ySQJWfF03/6ZzlCi3JPvfXDQ35T6PbD/5BvMDPmiXyWGqKUpZfRNus8VkG1G8TWwvHsVct9M47sZqLDuyzWVcwx613i8uxDsEIp9pVy4IxVl2UxvDJH1CPnNdvEhS3NeD9XfkMKMJtShHu1iVzB+j668W5SXsiv/N+psa2SIs+wuDY7FSirRg2Uwk9mlsXS+lSgV9B8BEcEIPEngA40MZCrEzNUdI0iYxaaqB6GNWeHiszOWz94rMkXk+FF3Ic75O4RyCmfXJmR8wYQs+rH4j7JJGzwTHXpYpV7H5V0OtMVIh6EUEvu3Veu/lkT4rsop55OHmcBmpKv8gGllX1P+g7H0+g35wn6Hz+FaSlkPvAuuSudgHFznOtfCowYhDUVp0TxTkcraLhkiROD4Qidw8r/HWh5CzrkfM9LWEBvziWFM77X/M=
+  - secure: SfJARcGrIyoemZ1jUENyX272acqmDTqQjk0LBjuzWxR5gqWp0Bn5TJS6x7niD8qzVC/n2FwR7F9FG3ll0cmToBgb8vh9763aL+ciA8cfk8iIUdjjQHNkywdhiLLw1EiR4TuUGOB3pV03WxMXl5JJTopJHScHgvTHfyhHS4vnQdrU3j6PwFDvVLsECyFiTCflDUgZ6zHocbIT4fqVjoBxy+5R0t9vyb4eE3IE3Lqjnac5oYVXm4KCpGUYDdCL0W2OlwPYgaBtWGetoWj/v6Kg4cjUVbKJHGK1q2nKyrCGPRvoKTqw83Pkfw1zkSdC0kCozMKr/gYsYBpt5IFXHaON/OkjvgdLbVKrpAHoauQ5KkWcTg9ntuUNndtg5pMaitBmd/J2AaMIxEde8ZvgfDR3sZdv0umFzLXJeAu/tbLR6ozP/BwOFpj89Lw0jNyNxJjdkLHP/4M2W5Ka0W1s+F6xNTahTuXdV32laPGN8SSmBrjzXwFOLOQTCgwlbD+YbTqAXUs+DLq/zIT6ZTaEfOdas+HTsu6H+cQfsmv9JlMP7PoT/CbL2tYe8kA2tjmxsQwUlVTcE1nP5ni5kHX+yXuClTzLimY+BOqU9SLJBKv6umygFqt/9nXk0YU6+UqfeDFCVzrMN0vnzWkmB0SQ3ElDC6pbb6NE/2RgmIy1pAUMDzY=
+  - secure: NPgEmEHVxJrLtaQ35IJnmQSbEHMqzSJt1l0OHX+a6VzfBiAP9Z4Qv1Ita/SQZpA4roLK2YkGr6TALndXgiY1EXU3e7ebcPnCgaQ6mCSJpAKtXhSGmArvysEmmxKSJjMxc8aBlJAeeEQlngXb0/pqA//kMw5KC+pPo+8O3UUbBs0qY9Q4FX3/cU+chv04tk5cSVR2P9nhOqY7LYm+FXjwF4vfjGKF7mWEcQUzTS8E2dRsertl/4law5O9/0gEW/9MaqyylPxq+0pS38sXP25zYotgEL3L8t0fupi58loPioDndardxVl35SXaatPuOxEdHr3Q0Q3dKVjbrTEx8bP7NgLY4nh7r5hqcEtvlM0xCAsfiOmGe0Dd0BFqo7kxzK/Fl6oI4MoEfvAYR+1QyBy9zOAJmjoj1hu/NEDAtT9NPxXJKl4A/iJPIYIt3lrMw34ewTSLl16ERojx6CZfSqg58eyUndmmOPBB8b4rIX0fQj5/ShsoLbM/rmOmDpPXN0QGu2EHhtR2eCbpdan84VfPifi5LjPH42PjdUxpJ+rOHZJHLesJ5JwQLFLrnDf7Bg96iucyz1QMpNFoqN+q4YDARot2zdw+1ISjgPx3bhHu8ly8oT1UYNl5mMjv7zfNDvsmBycpQEBQWGvIioRfx6dM/EtZy2E/og5Ci0j9v19YI2E=
+  - secure: f52LTJZfcwkCnlK0ig1/iskZxeTRyU5dGBjuF2f4CKXdV1ajlfjlNj/oX8V7eVYvSVIz1IkE9h2pi2x+P7Jp63ITdzNPAQlbaiqJqEG15g4JicRvTFgKba+dKfZL8lK+mHd/QZE6dd1QsjPuU2BbHuqKccS3NyrqhFkSFGD2eQtft6fXg7O7uFMEdEss8J2H9rYkmV/jH8H3xkaxH/EBeHa03NCMUBWampQ69SKstg+vSMXQCvrQWLbfpq+/K5SWnAW4UoWrvtlFT6lyFeWUTEXmHP8W8/DgubaMZCbMqrautsw4Qjf5VGucgitMVXEkT14MZIJtvhqjF+KPER8D0oPATDp5lb6k7iI100sZkpESGed2rnTwxP4rOrYpT1K3xju1iaTjUpVOZ55yCHCPyB5G1e5BYCEoXlnO2YzLpuGJJs02Mb/cSLL/JgyjPqI5HguN1mz1SsCG9yxknuEdjwwhyHDYudi3FZ8f8RooOE/SQBA7zycvSd5OqvlzgnN3hzG7cA1Bje3E7v1b4rBjF0sfkPpjadvleq2rex8THhFQeVFlrGvRS777/+xg5IA9C5wLaKqAvcbTMV4hzWO89L7W/+13vfH6JecdQGkbxmdr+Y/bDn1SwflfFcUiju9/p64T2rYFGDbwu5jDKV5KGhbMFbUJxds0ETyUKpGOCRs=
+  - secure: BDZ2Skv2cJU3mzK9fYISp3qWjJHXKUtQIUw6dYN0MYN78LTApjF1af3NLtjhPP92zcdAefAYNk0Dic7K+nEMS6fxoenVbeH3USUXWgIiu8iRt8wfMOOJiCzmoU8yoM+8HFA0/CgAfgkeqhW+zKTL3auIxwCGaVSElBmWB6tefyXEGF2fci/bXG3bDprQ4kZUAyvON8qABXQ+dsX225YwCP46CMTjRK+C1wj5zb6DDQC04sHRmFOMm3QATLXRXI7Lt5ju+Wkbg9JZqfL2Zo6GvZo0J2mpUijznGaCSSdy+r5edfnMmilPxeKUXODTyabSb33Lbinbp2ITUU3cIFGY76PVPYU7a3JN+vssPgpZm1M3kCDUUV71tipbl32//PjBfN0gYqCEGO6yvzklJIHjdgrd8zX2dDyY+vk7aZPDFySoVd5Dr2jGvCNOyE7bDD/9Ahis7VvkAMdU1S2NrLAUin9ZQaW4LjiiAWj3aXACI1i9FwA5SuGcQoPBnFsK7K3TkpgeSxG1YnaegOjm40QJJ8VqexMcBHDgMM2RECyJfhJbBM4JJpQngL8as0aO8HIbOlNSgyG3VCra4Lsa2GHWoOq1A7JI+HztdhCFICTEAcA6IqsFYsdQR/PC50FJawfqNuyDcXw2OtomYUe77/KsI5oOOL/cDYtNvasWW/brrCg=
+  - secure: aXiJ7R2Pe086+B5x5cuPmK0kG3f/G0vSryQEgmNvlU+Gpr0KaFsV76xzLyy0b8SOeVxA055O7/X7ZA4Pd/IKc5qz/98RX2gieozPDvwryfZ1weZbEYkjVBnWpzoIadN8jqTzN4LDf5nwTDcdEs45gU4uQgYBMWYvgPUBE20oD9iI7052qo/tknlQusPUisRiISuBpgnB1twnLk81ZZe3lRJdxmAOl6thaQj37UbqfvxCgyZVeVq3Y0tY6xl50/KNMyiecluoZuKu0h4EZGYk1T6UaheQ+MQRWNedJIsXeXhG8WoWrmCy5ZInzXWjsKwU4daLaaSgLzgcoNkYzyjAWwPNsepjiyAdXnVpzyifjdNm+LfB9Al5weAllFfzl580yAWVZlf/g0+X/dDxGxZME1jE53BcvUnvj/9cp0mox8JdjvB+ARHmxbPDUCfC9mcBPg0W24duk3brjT6BuR/xUMKEuydO0WDRAgOKopsAQalf/w+vEXQ+liKFs2CSwuo7CEUbtW+cTKz3XBQq6Ws5PAEeHmE3xy8wE5mafx0n6vv3oPGRQiTiko+CwUPrkqUmHzcHf+0N82RCcnT1CdALO7E+fowab5GVdOffUMKurQs5/pi8ux2YyNbS7qSQopAytV2rjiOei6ZDefExhWOWJWej9mPfDf9MGqlDkzbB8UE=
\ No newline at end of file
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/CHANGELOG.md b/vendor/github.com/aliyun/aliyun-oss-go-sdk/CHANGELOG.md
new file mode 100644 (file)
index 0000000..7f14c0e
--- /dev/null
@@ -0,0 +1,247 @@
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.1.6 日期:2021-01-13
+### 变更内容
+- 增加:增加worm接口
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.1.5 日期:2020-11-19
+### 变更内容
+- 增加:增加ListObjectsV2接口
+- 增加: 增加RestoreObjectXML接口
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.1.4 日期:2020-07-24
+### 变更内容
+- 修复:lifecycle配置支持输入LifecycleVersionTransition数组
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.1.3 日期:2020-07-10
+### 变更内容
+- 修复:lifecycle支持冷归档(ColdArchive)
+
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.1.2 日期:2020-06-19
+### 变更内容
+- 增加:支持禁止http跳转功能(go1.7.0版本及以上)
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.1.1 日期:2020-06-04
+### 变更内容
+- 增加:支持国密byok
+- 增加:支持异步任务的设置和读取
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.1.0 日期:2020-04-21
+### 变更内容
+- 增加:支持客户端加密、清单、冷归档功能
+- 增加:tcp连接增加keepalive心跳选项
+- 优化: 分块上传事件通知优化
+
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.0.8 日期:2020-04-09
+### 变更内容
+- 增加:支持用户传入自定义的header和param参数
+- 增加:增加对X-Oss-Range-Behavior支持
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.0.7 日期:2020-03-11
+### 变更内容
+- 增加:支持OSS V2 签名
+- 增加:增加SetBucketWebsiteXml接口,支持直接传入xml文件内容
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.0.6 日期:2020-02-15
+### 变更内容
+- 修复:CopyFile接口需要支持服务端加密功能
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.0.5 日期:2020-01-03
+### 变更内容
+- 增加:增加禁止同名覆盖选项X-Oss-Forbid-Overwrite
+- 增加:增加分块上传参数sequential, 支持分块上传返回md5校验值
+
+## 版本号:v2.0.4 日期:2019-11-13
+### 变更内容
+- 增加:SSR 对bucket 和 endpoint 做合法性校验,不符合要求要直接提示错误。
+- 增加:select object 功能merge
+- 增加:断点续传文件支持多版本
+- 增加:lifecycle 支持多版本
+- 修复:断点续传文件中的时间比较方式优化
+- 修复:修复断点上传不支持服务端加密的bug
+
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.0.3 日期:2019-09-17
+### 变更内容
+- 修复:不支持分块上传归档object
+- 增加:增加绑定客户端ip地址
+- 增加: 增加更多的mime type类型
+
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.0.2 日期:2019-08-06
+### 变更内容
+- 修复:proxy代理不支持https请求
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.0.1 日期:2019-07-11
+### 变更内容
+- 增加:增加qos相关api
+- 增加:增加payment相关api
+- 增加:增加自定义获取AccessKeyID、AccessKeySecret、SecurityToken
+- 增加: 增加http请求限速option
+
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v2.0.0 日期:2019-06-18
+### 变更内容
+- 增加:增加各个接口对versioning的支持
+- 增加:增加设置、查询、删除bucket policy接口
+- 增加: 增加设置website详细配置接口: SetBucketWebsiteDetail
+- 增加: 增加Bucket OptionsMethod 接口
+
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v1.9.8 日期:2019-05-25
+### 变更内容
+- 增加:增加设置、查询、删除bucket tagging接口
+
+# ChangeLog - Aliyun OSS SDK for Go
+## 版本号:v1.9.7 日期:2019-05-22
+### 变更内容
+- 增加:增加设置、查询、删除object tagging接口
+- 增加:增加设置、查询、删除bucket encryption接口
+- 增加:增加获取bucket统计信息接口
+
+## 版本号:v1.9.6 日期:2019-04-15
+### 变更内容
+- 变更:扩展lifecycle功能,提供设置AbortMutipartUpload和Transitions两种规则的生命周期管理的处理
+- 修复:测试用例BucketName使用固定前缀+随机的字符串
+- 修复:测试用例ObjectName使用固定前缀+随机字符串
+- 修复:测试用例有关bucket相关的异步操作,统一定义sleep时间
+- 修复:测试集结束后,列出bucket内的所有对象并删除所有测试的对象
+- 修复:测试集结束后,列出bucket内的所有未上传完成的分片并删除所有测试过程中产生的为上传完成的分片
+- 修复:支持上传webp类型的对象时从对象的后缀名字自动解析对应的content-type并设置content-type
+- 变更:增加在put/copy/append等接口时时设置对象的存储类型的sample
+- 修复:sample示例中的配置项的值改为直接从环境变量读取
+
+## 版本号:1.9.5 日期:2019-03-08
+### 变更内容
+- 变更:增加了限速上传功能
+
+## 版本号:1.9.4 日期:2019-01-25
+### 变更内容
+- 修复:在开启日志后,如果接口返回错误readResponseBody函数被调用两次
+- 变更:增加livechannel功能各个api接口
+
+## 版本号:1.9.3 日期:2019-01-10
+### 变更内容
+- 修复:分片上传时传入partSize值不对是的提示信息不准确的问题
+- 修复:仅仅在使用userAgent的时候初始化它的值
+- 变更:添加ContentLanguage选项
+- 变更:支持设置最大的空闲连接个数
+- 变更:当配置的endpoint不对时,输出的错误信息将会打印出正确的endpoint
+- 变更:支持ServerSideEncryptionKeyID选项,允许用户传入kms-id
+- 变更:添加日志模块,支持设置日志级别
+
+## 版本号:1.9.2 日期:2018-11-16
+### 变更内容
+- 变更:添加支持设置request Payer的option
+- 变更:添加支持设置checkpoint目录的option
+- 变更:getobjectmeta接口增加options参数,可以支持传入option选项
+- 变更:listobjecs接口增加options参数,可以支持传入option选项
+- 变更:listmultipartuploads接口增加options参数, 可以支持传入option选项
+- 修复:解决调用接口返回出错时,且返回的http body为空时,打印错误消息不包含"request_id"的问题
+- 变更:abortmultipartupload接口增加options参数, 可以支持传入option选项
+- 变更:completemultipartupload接口增加options参数, 可以支持传入option选项
+
+## 版本号:1.9.1 日期:2018-09-17
+### 变更内容
+ - 变更:支持ipv6
+ - 变更:支持修改对象的存储类型
+ - 修复:修改sample中GetBucketReferer方法名拼写错误
+ - 修复:修复NopCloser在close的时候并不释放内存的内存泄漏问题
+ - 变更:增加ProcessObject接口
+ - 修复:修改图片处理接口参数拼写错误导致无法处理的bug
+ - 修复:增加ListUploadedParts接口的options选项
+ - 修复:增加Callback&CallbackVal选项,支持回调使用
+ - 修复:GetObject接口返回Response,支持用户读取crc等返回值
+ - 修复:当以压缩格式返回数据时,GetObject接口不校验crc
+
+## 版本号:1.9.0 日期:2018-06-15
+### 变更内容
+ - 变更:国际化
+
+## 版本号:1.8.0 日期:2017-12-12
+### 变更内容
+ - 变更:空闲链接关闭时间调整为50秒
+ - 修复:修复临时账号使用SignURL的问题
+
+## 版本号:1.7.0 日期:2017-09-25
+### 变更内容
+ - 增加:DownloadFile支持CRC校验
+ - 增加:STS测试用例
+
+## 版本号:1.6.0 日期:2017-09-01
+### 变更内容
+ - 修复:URL中特殊字符的编码问题
+ - 变更:不再支持Golang 1.4
+## 版本号:1.5.1 日期:2017-08-04
+### 变更内容
+ - 修复:SignURL中Key编码的问题
+ - 修复:DownloadFile下载完成后rename失败的问题
+## 版本号:1.5.0 日期:2017-07-25
+### 变更内容
+ - 增加:支持生成URL签名
+ - 增加:GetObject支持ResponseContentType等选项
+ - 修复:DownloadFile去除分片小于5GB的限制
+ - 修复:AppendObject在appendPosition不正确时发生panic
+
+## 版本号:1.4.0 日期:2017-05-23
+### 变更内容
+ - 增加:支持符号链接symlink
+ - 增加:支持RestoreObject
+ - 增加:CreateBucket支持StorageClass
+ - 增加:支持范围读NormalizedRange
+ - 修复:IsObjectExist使用GetObjectMeta实现
+
+## 版本号:1.3.0 日期:2017-01-13
+### 变更内容
+ - 增加:上传下载支持进度条功能
+
+## 版本号:1.2.3 日期:2016-12-28
+### 变更内容
+ - 修复:每次请求使用一个http.Client修改为共用http.Client
+
+## 版本号:1.2.2 日期:2016-12-10
+### 变更内容
+ - 修复:GetObjectToFile/DownloadFile使用临时文件下载,成功后重命名成下载文件
+ - 修复:新建的下载文件权限修改为0664
+
+## 版本号:1.2.1 日期:2016-11-11
+### 变更内容
+ - 修复:只有当OSS返回x-oss-hash-crc64ecma头部时,才对上传的文件进行CRC64完整性校验
+
+## 版本号:1.2.0 日期:2016-10-18
+### 变更内容
+ - 增加:支持CRC64校验
+ - 增加:支持指定Useragent
+ - 修复:计算MD5占用内存大的问题
+ - 修复:CopyObject时Object名称没有URL编码的问题
+
+## 版本号:1.1.0 日期:2016-08-09
+### 变更内容
+ - 增加:支持代理服务器
+
+## 版本号:1.0.0 日期:2016-06-24
+### 变更内容
+ - 增加:断点分片复制接口Bucket.CopyFile
+ - 增加:Bucket间复制接口Bucket.CopyObjectTo、Bucket.CopyObjectFrom
+ - 增加:Client.GetBucketInfo接口
+ - 增加:Bucket.UploadPartCopy支持Bucket间复制
+ - 修复:断点上传、断点下载出错后,协程不退出的Bug
+ - 删除:接口Bucket.CopyObjectToBucket
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/LICENSE b/vendor/github.com/aliyun/aliyun-oss-go-sdk/LICENSE
new file mode 100644 (file)
index 0000000..d46e9d1
--- /dev/null
@@ -0,0 +1,14 @@
+Copyright (c) 2015 aliyun.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/README-CN.md b/vendor/github.com/aliyun/aliyun-oss-go-sdk/README-CN.md
new file mode 100644 (file)
index 0000000..dd1c63a
--- /dev/null
@@ -0,0 +1,169 @@
+# Aliyun OSS SDK for Go
+
+[![GitHub version](https://badge.fury.io/gh/aliyun%2Faliyun-oss-go-sdk.svg)](https://badge.fury.io/gh/aliyun%2Faliyun-oss-go-sdk)
+[![Build Status](https://travis-ci.org/aliyun/aliyun-oss-go-sdk.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-oss-go-sdk)
+[![Coverage Status](https://coveralls.io/repos/github/aliyun/aliyun-oss-go-sdk/badge.svg?branch=master)](https://coveralls.io/github/aliyun/aliyun-oss-go-sdk?branch=master)
+
+## [README in English](https://github.com/aliyun/aliyun-oss-go-sdk/blob/master/README.md)
+
+## 关于
+> - 此Go SDK基于[阿里云对象存储服务](http://www.aliyun.com/product/oss/)官方API构建。
+> - 阿里云对象存储(Object Storage Service,简称OSS),是阿里云对外提供的海量,安全,低成本,高可靠的云存储服务。
+> - OSS适合存放任意文件类型,适合各种网站、开发企业及开发者使用。
+> - 使用此SDK,用户可以方便地在任何应用、任何时间、任何地点上传,下载和管理数据。
+
+## 版本
+> - Current version: v2.1.6
+
+## 运行环境
+> - Go 1.5及以上。
+
+## 安装方法
+### GitHub安装
+> - 执行命令`go get github.com/aliyun/aliyun-oss-go-sdk/oss`获取远程代码包。
+> - 在您的代码中使用`import "github.com/aliyun/aliyun-oss-go-sdk/oss"`引入OSS Go SDK的包。
+
+## 快速使用
+#### 获取存储空间列表(List Bucket)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    lsRes, err := client.ListBuckets()
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    for _, bucket := range lsRes.Buckets {
+        fmt.Println("Buckets:", bucket.Name)
+    }
+```
+
+#### 创建存储空间(Create Bucket)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.CreateBucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+    
+#### 删除存储空间(Delete Bucket)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.DeleteBucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+#### 上传文件(Put Object)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.PutObjectFromFile("my-object", "LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+#### 下载文件 (Get Object)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.GetObjectToFile("my-object", "LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+#### 获取文件列表(List Objects)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    lsRes, err := bucket.ListObjects()
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    for _, object := range lsRes.Objects {
+        fmt.Println("Objects:", object.Key)
+    }
+```
+    
+#### 删除文件(Delete Object)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.DeleteObject("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+#### 其它
+更多的示例程序,请参看OSS Go SDK安装路径(即GOPATH变量中的第一个路径)下的`src\github.com\aliyun\aliyun-oss-go-sdk\sample`,该目录下为示例程序,
+或者参看`https://github.com/aliyun/aliyun-oss-go-sdk`下sample目录中的示例文件。
+
+## 注意事项
+### 运行sample
+> - 拷贝示例文件。到OSS Go SDK的安装路径(即GOPATH变量中的第一个路径),进入OSS Go SDK的代码目录`src\github.com\aliyun\aliyun-oss-go-sdk`,
+把其下的sample目录和sample.go复制到您的测试工程src目录下。
+> - 修改sample/config.go里的endpoint、AccessKeyId、AccessKeySecret、BucketName等配置。
+> - 请在您的工程目录下执行`go run src/sample.go`。
+
+## 联系我们
+> - [阿里云OSS官方网站](http://oss.aliyun.com)
+> - [阿里云OSS官方论坛](http://bbs.aliyun.com)
+> - [阿里云OSS官方文档中心](http://www.aliyun.com/product/oss#Docs)
+> - 阿里云官方技术支持:[提交工单](https://workorder.console.aliyun.com/#/ticket/createIndex)
+
+## 作者
+> - [Yubin Bai](https://github.com/baiyubin)
+> - [Guozhong Han](https://github.com/hangzws)
+
+## License
+> - MIT License, see [license file](LICENSE)
+
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/README.md b/vendor/github.com/aliyun/aliyun-oss-go-sdk/README.md
new file mode 100644 (file)
index 0000000..5f06496
--- /dev/null
@@ -0,0 +1,168 @@
+# Alibaba Cloud OSS SDK for Go
+
+[![GitHub Version](https://badge.fury.io/gh/aliyun%2Faliyun-oss-go-sdk.svg)](https://badge.fury.io/gh/aliyun%2Faliyun-oss-go-sdk)
+[![Build Status](https://travis-ci.org/aliyun/aliyun-oss-go-sdk.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-oss-go-sdk)
+[![Coverage Status](https://coveralls.io/repos/github/aliyun/aliyun-oss-go-sdk/badge.svg?branch=master)](https://coveralls.io/github/aliyun/aliyun-oss-go-sdk?branch=master)
+
+## [README in Chinese](https://github.com/aliyun/aliyun-oss-go-sdk/blob/master/README-CN.md)
+
+## About
+> - This Go SDK is based on the official APIs of [Alibaba Cloud OSS](http://www.aliyun.com/product/oss/).
+> - Alibaba Cloud Object Storage Service (OSS) is a cloud storage service provided by Alibaba Cloud, featuring massive capacity, security, a low cost, and high reliability. 
+> - The OSS can store any type of files and therefore applies to various websites, development enterprises and developers.
+> - With this SDK, you can upload, download and manage data on any app anytime and anywhere conveniently. 
+
+## Version
+> - Current version: v2.1.6
+
+## Running Environment
+> - Go 1.5 or above. 
+
+## Installing
+### Install the SDK through GitHub
+> - Run the 'go get github.com/aliyun/aliyun-oss-go-sdk/oss' command to get the remote code package.
+> - Use 'import "github.com/aliyun/aliyun-oss-go-sdk/oss"' in your code to introduce OSS Go SDK package.
+
+## Getting Started
+### List Bucket
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    lsRes, err := client.ListBuckets()
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    for _, bucket := range lsRes.Buckets {
+        fmt.Println("Buckets:", bucket.Name)
+    }
+```
+
+### Create Bucket
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.CreateBucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+    
+### Delete Bucket
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.DeleteBucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+### Put Object
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.PutObjectFromFile("my-object", "LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+### Get Object
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.GetObjectToFile("my-object", "LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+### List Objects
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    lsRes, err := bucket.ListObjects()
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    for _, object := range lsRes.Objects {
+        fmt.Println("Objects:", object.Key)
+    }
+```
+    
+### Delete Object
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.DeleteObject("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+##  Complete Example
+More example projects can be found at 'src\github.com\aliyun\aliyun-oss-go-sdk\sample' under the installation path of the OSS Go SDK (the first path of the GOPATH variable). The directory contains example projects. 
+Or you can refer to the example objects in the sample directory under 'https://github.com/aliyun/aliyun-oss-go-sdk'.
+
+### Running Example
+> - Copy the example file. Go to the installation path of OSS Go SDK (the first path of the GOPATH variable), enter the code directory of the OSS Go SDK, namely 'src\github.com\aliyun\aliyun-oss-go-sdk',
+and copy the sample directory and sample.go to the src directory of your test project.
+> - Modify the  endpoint, AccessKeyId, AccessKeySecret and BucketName configuration settings in sample/config.go.
+> - Run 'go run src/sample.go' under your project directory.
+
+## Contacting us
+> - [Alibaba Cloud OSS official website](http://oss.aliyun.com).
+> - [Alibaba Cloud OSS official forum](http://bbs.aliyun.com).
+> - [Alibaba Cloud OSS official documentation center](http://www.aliyun.com/product/oss#Docs).
+> - Alibaba Cloud official technical support: [Submit a ticket](https://workorder.console.aliyun.com/#/ticket/createIndex). 
+
+## Author
+> - [Yubin Bai](https://github.com/baiyubin)
+> - [Guozhong Han](https://github.com/hangzws)
+
+## License
+> - MIT License, see [license file](LICENSE)
+
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/auth.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/auth.go
new file mode 100644 (file)
index 0000000..bc1e4fa
--- /dev/null
@@ -0,0 +1,190 @@
+package oss
+
+import (
+       "bytes"
+       "crypto/hmac"
+       "crypto/sha1"
+       "crypto/sha256"
+       "encoding/base64"
+       "fmt"
+       "hash"
+       "io"
+       "net/http"
+       "sort"
+       "strconv"
+       "strings"
+)
+
+// headerSorter defines the key-value structure for storing the sorted data in signHeader.
+type headerSorter struct {
+       Keys []string
+       Vals []string
+}
+
+// getAdditionalHeaderKeys get exist key in http header
+func (conn Conn) getAdditionalHeaderKeys(req *http.Request) ([]string, map[string]string) {
+       var keysList []string
+       keysMap := make(map[string]string)
+       srcKeys := make(map[string]string)
+
+       for k := range req.Header {
+               srcKeys[strings.ToLower(k)] = ""
+       }
+
+       for _, v := range conn.config.AdditionalHeaders {
+               if _, ok := srcKeys[strings.ToLower(v)]; ok {
+                       keysMap[strings.ToLower(v)] = ""
+               }
+       }
+
+       for k := range keysMap {
+               keysList = append(keysList, k)
+       }
+       sort.Strings(keysList)
+       return keysList, keysMap
+}
+
+// signHeader signs the header and sets it as the authorization header.
+func (conn Conn) signHeader(req *http.Request, canonicalizedResource string) {
+       akIf := conn.config.GetCredentials()
+       authorizationStr := ""
+       if conn.config.AuthVersion == AuthV2 {
+               additionalList, _ := conn.getAdditionalHeaderKeys(req)
+               if len(additionalList) > 0 {
+                       authorizationFmt := "OSS2 AccessKeyId:%v,AdditionalHeaders:%v,Signature:%v"
+                       additionnalHeadersStr := strings.Join(additionalList, ";")
+                       authorizationStr = fmt.Sprintf(authorizationFmt, akIf.GetAccessKeyID(), additionnalHeadersStr, conn.getSignedStr(req, canonicalizedResource, akIf.GetAccessKeySecret()))
+               } else {
+                       authorizationFmt := "OSS2 AccessKeyId:%v,Signature:%v"
+                       authorizationStr = fmt.Sprintf(authorizationFmt, akIf.GetAccessKeyID(), conn.getSignedStr(req, canonicalizedResource, akIf.GetAccessKeySecret()))
+               }
+       } else {
+               // Get the final authorization string
+               authorizationStr = "OSS " + akIf.GetAccessKeyID() + ":" + conn.getSignedStr(req, canonicalizedResource, akIf.GetAccessKeySecret())
+       }
+
+       // Give the parameter "Authorization" value
+       req.Header.Set(HTTPHeaderAuthorization, authorizationStr)
+}
+
+func (conn Conn) getSignedStr(req *http.Request, canonicalizedResource string, keySecret string) string {
+       // Find out the "x-oss-"'s address in header of the request
+       ossHeadersMap := make(map[string]string)
+       additionalList, additionalMap := conn.getAdditionalHeaderKeys(req)
+       for k, v := range req.Header {
+               if strings.HasPrefix(strings.ToLower(k), "x-oss-") {
+                       ossHeadersMap[strings.ToLower(k)] = v[0]
+               } else if conn.config.AuthVersion == AuthV2 {
+                       if _, ok := additionalMap[strings.ToLower(k)]; ok {
+                               ossHeadersMap[strings.ToLower(k)] = v[0]
+                       }
+               }
+       }
+       hs := newHeaderSorter(ossHeadersMap)
+
+       // Sort the ossHeadersMap by the ascending order
+       hs.Sort()
+
+       // Get the canonicalizedOSSHeaders
+       canonicalizedOSSHeaders := ""
+       for i := range hs.Keys {
+               canonicalizedOSSHeaders += hs.Keys[i] + ":" + hs.Vals[i] + "\n"
+       }
+
+       // Give other parameters values
+       // when sign URL, date is expires
+       date := req.Header.Get(HTTPHeaderDate)
+       contentType := req.Header.Get(HTTPHeaderContentType)
+       contentMd5 := req.Header.Get(HTTPHeaderContentMD5)
+
+       // default is v1 signature
+       signStr := req.Method + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedOSSHeaders + canonicalizedResource
+       h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(keySecret))
+
+       // v2 signature
+       if conn.config.AuthVersion == AuthV2 {
+               signStr = req.Method + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedOSSHeaders + strings.Join(additionalList, ";") + "\n" + canonicalizedResource
+               h = hmac.New(func() hash.Hash { return sha256.New() }, []byte(keySecret))
+       }
+
+       // convert sign to log for easy to view
+       if conn.config.LogLevel >= Debug {
+               var signBuf bytes.Buffer
+               for i := 0; i < len(signStr); i++ {
+                       if signStr[i] != '\n' {
+                               signBuf.WriteByte(signStr[i])
+                       } else {
+                               signBuf.WriteString("\\n")
+                       }
+               }
+               conn.config.WriteLog(Debug, "[Req:%p]signStr:%s\n", req, signBuf.String())
+       }
+
+       io.WriteString(h, signStr)
+       signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
+
+       return signedStr
+}
+
+func (conn Conn) getRtmpSignedStr(bucketName, channelName, playlistName string, expiration int64, keySecret string, params map[string]interface{}) string {
+       if params[HTTPParamAccessKeyID] == nil {
+               return ""
+       }
+
+       canonResource := fmt.Sprintf("/%s/%s", bucketName, channelName)
+       canonParamsKeys := []string{}
+       for key := range params {
+               if key != HTTPParamAccessKeyID && key != HTTPParamSignature && key != HTTPParamExpires && key != HTTPParamSecurityToken {
+                       canonParamsKeys = append(canonParamsKeys, key)
+               }
+       }
+
+       sort.Strings(canonParamsKeys)
+       canonParamsStr := ""
+       for _, key := range canonParamsKeys {
+               canonParamsStr = fmt.Sprintf("%s%s:%s\n", canonParamsStr, key, params[key].(string))
+       }
+
+       expireStr := strconv.FormatInt(expiration, 10)
+       signStr := expireStr + "\n" + canonParamsStr + canonResource
+
+       h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(keySecret))
+       io.WriteString(h, signStr)
+       signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
+       return signedStr
+}
+
+// newHeaderSorter is an additional function for function SignHeader.
+func newHeaderSorter(m map[string]string) *headerSorter {
+       hs := &headerSorter{
+               Keys: make([]string, 0, len(m)),
+               Vals: make([]string, 0, len(m)),
+       }
+
+       for k, v := range m {
+               hs.Keys = append(hs.Keys, k)
+               hs.Vals = append(hs.Vals, v)
+       }
+       return hs
+}
+
+// Sort is an additional function for function SignHeader.
+func (hs *headerSorter) Sort() {
+       sort.Sort(hs)
+}
+
+// Len is an additional function for function SignHeader.
+func (hs *headerSorter) Len() int {
+       return len(hs.Vals)
+}
+
+// Less is an additional function for function SignHeader.
+func (hs *headerSorter) Less(i, j int) bool {
+       return bytes.Compare([]byte(hs.Keys[i]), []byte(hs.Keys[j])) < 0
+}
+
+// Swap is an additional function for function SignHeader.
+func (hs *headerSorter) Swap(i, j int) {
+       hs.Vals[i], hs.Vals[j] = hs.Vals[j], hs.Vals[i]
+       hs.Keys[i], hs.Keys[j] = hs.Keys[j], hs.Keys[i]
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket.go
new file mode 100644 (file)
index 0000000..430252c
--- /dev/null
@@ -0,0 +1,1289 @@
+package oss
+
+import (
+       "bytes"
+       "crypto/md5"
+       "encoding/base64"
+       "encoding/xml"
+       "fmt"
+       "hash"
+       "hash/crc64"
+       "io"
+       "net/http"
+       "net/url"
+       "os"
+       "strconv"
+       "strings"
+       "time"
+)
+
+// Bucket implements the operations of object.
+type Bucket struct {
+       Client     Client
+       BucketName string
+}
+
+// PutObject creates a new object and it will overwrite the original one if it exists already.
+//
+// objectKey    the object key in UTF-8 encoding. The length must be between 1 and 1023, and cannot start with "/" or "\".
+// reader    io.Reader instance for reading the data for uploading
+// options    the options for uploading the object. The valid options here are CacheControl, ContentDisposition, ContentEncoding
+//            Expires, ServerSideEncryption, ObjectACL and Meta. Refer to the link below for more details.
+//            https://help.aliyun.com/document_detail/oss/api-reference/object/PutObject.html
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) PutObject(objectKey string, reader io.Reader, options ...Option) error {
+       opts := AddContentType(options, objectKey)
+
+       request := &PutObjectRequest{
+               ObjectKey: objectKey,
+               Reader:    reader,
+       }
+       resp, err := bucket.DoPutObject(request, opts)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return err
+}
+
+// PutObjectFromFile creates a new object from the local file.
+//
+// objectKey    object key.
+// filePath    the local file path to upload.
+// options    the options for uploading the object. Refer to the parameter options in PutObject for more details.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) PutObjectFromFile(objectKey, filePath string, options ...Option) error {
+       fd, err := os.Open(filePath)
+       if err != nil {
+               return err
+       }
+       defer fd.Close()
+
+       opts := AddContentType(options, filePath, objectKey)
+
+       request := &PutObjectRequest{
+               ObjectKey: objectKey,
+               Reader:    fd,
+       }
+       resp, err := bucket.DoPutObject(request, opts)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return err
+}
+
+// DoPutObject does the actual upload work.
+//
+// request    the request instance for uploading an object.
+// options    the options for uploading an object.
+//
+// Response    the response from OSS.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) DoPutObject(request *PutObjectRequest, options []Option) (*Response, error) {
+       isOptSet, _, _ := IsOptionSet(options, HTTPHeaderContentType)
+       if !isOptSet {
+               options = AddContentType(options, request.ObjectKey)
+       }
+
+       listener := GetProgressListener(options)
+
+       params := map[string]interface{}{}
+       resp, err := bucket.do("PUT", request.ObjectKey, params, options, request.Reader, listener)
+       if err != nil {
+               return nil, err
+       }
+
+       if bucket.GetConfig().IsEnableCRC {
+               err = CheckCRC(resp, "DoPutObject")
+               if err != nil {
+                       return resp, err
+               }
+       }
+
+       err = CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+
+       return resp, err
+}
+
+// GetObject downloads the object.
+//
+// objectKey    the object key.
+// options    the options for downloading the object. The valid values are: Range, IfModifiedSince, IfUnmodifiedSince, IfMatch,
+//            IfNoneMatch, AcceptEncoding. For more details, please check out:
+//            https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html
+//
+// io.ReadCloser    reader instance for reading data from response. It must be called close() after the usage and only valid when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) GetObject(objectKey string, options ...Option) (io.ReadCloser, error) {
+       result, err := bucket.DoGetObject(&GetObjectRequest{objectKey}, options)
+       if err != nil {
+               return nil, err
+       }
+
+       return result.Response, nil
+}
+
+// GetObjectToFile downloads the data to a local file.
+//
+// objectKey    the object key to download.
+// filePath    the local file to store the object data.
+// options    the options for downloading the object. Refer to the parameter options in method GetObject for more details.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) GetObjectToFile(objectKey, filePath string, options ...Option) error {
+       tempFilePath := filePath + TempFileSuffix
+
+       // Calls the API to actually download the object. Returns the result instance.
+       result, err := bucket.DoGetObject(&GetObjectRequest{objectKey}, options)
+       if err != nil {
+               return err
+       }
+       defer result.Response.Close()
+
+       // If the local file does not exist, create a new one. If it exists, overwrite it.
+       fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, FilePermMode)
+       if err != nil {
+               return err
+       }
+
+       // Copy the data to the local file path.
+       _, err = io.Copy(fd, result.Response.Body)
+       fd.Close()
+       if err != nil {
+               return err
+       }
+
+       // Compares the CRC value
+       hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange)
+       encodeOpt, _ := FindOption(options, HTTPHeaderAcceptEncoding, nil)
+       acceptEncoding := ""
+       if encodeOpt != nil {
+               acceptEncoding = encodeOpt.(string)
+       }
+       if bucket.GetConfig().IsEnableCRC && !hasRange && acceptEncoding != "gzip" {
+               result.Response.ClientCRC = result.ClientCRC.Sum64()
+               err = CheckCRC(result.Response, "GetObjectToFile")
+               if err != nil {
+                       os.Remove(tempFilePath)
+                       return err
+               }
+       }
+
+       return os.Rename(tempFilePath, filePath)
+}
+
+// DoGetObject is the actual API that gets the object. It's the internal function called by other public APIs.
+//
+// request    the request to download the object.
+// options    the options for downloading the file. Checks out the parameter options in method GetObject.
+//
+// GetObjectResult    the result instance of getting the object.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) DoGetObject(request *GetObjectRequest, options []Option) (*GetObjectResult, error) {
+       params, _ := GetRawParams(options)
+       resp, err := bucket.do("GET", request.ObjectKey, params, options, nil, nil)
+       if err != nil {
+               return nil, err
+       }
+
+       result := &GetObjectResult{
+               Response: resp,
+       }
+
+       // CRC
+       var crcCalc hash.Hash64
+       hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange)
+       if bucket.GetConfig().IsEnableCRC && !hasRange {
+               crcCalc = crc64.New(CrcTable())
+               result.ServerCRC = resp.ServerCRC
+               result.ClientCRC = crcCalc
+       }
+
+       // Progress
+       listener := GetProgressListener(options)
+
+       contentLen, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderContentLength), 10, 64)
+       resp.Body = TeeReader(resp.Body, crcCalc, contentLen, listener, nil)
+
+       return result, nil
+}
+
+// CopyObject copies the object inside the bucket.
+//
+// srcObjectKey    the source object to copy.
+// destObjectKey    the target object to copy.
+// options    options for copying an object. You can specify the conditions of copy. The valid conditions are CopySourceIfMatch,
+//            CopySourceIfNoneMatch, CopySourceIfModifiedSince, CopySourceIfUnmodifiedSince, MetadataDirective.
+//            Also you can specify the target object's attributes, such as CacheControl, ContentDisposition, ContentEncoding, Expires,
+//            ServerSideEncryption, ObjectACL, Meta. Refer to the link below for more details :
+//            https://help.aliyun.com/document_detail/oss/api-reference/object/CopyObject.html
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) {
+       var out CopyObjectResult
+
+       //first find version id
+       versionIdKey := "versionId"
+       versionId, _ := FindOption(options, versionIdKey, nil)
+       if versionId == nil {
+               options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey)))
+       } else {
+               options = DeleteOption(options, versionIdKey)
+               options = append(options, CopySourceVersion(bucket.BucketName, url.QueryEscape(srcObjectKey), versionId.(string)))
+       }
+
+       params := map[string]interface{}{}
+       resp, err := bucket.do("PUT", destObjectKey, params, options, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// CopyObjectTo copies the object to another bucket.
+//
+// srcObjectKey    source object key. The source bucket is Bucket.BucketName .
+// destBucketName    target bucket name.
+// destObjectKey    target object name.
+// options    copy options, check out parameter options in function CopyObject for more details.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) CopyObjectTo(destBucketName, destObjectKey, srcObjectKey string, options ...Option) (CopyObjectResult, error) {
+       return bucket.copy(srcObjectKey, destBucketName, destObjectKey, options...)
+}
+
+//
+// CopyObjectFrom copies the object to another bucket.
+//
+// srcBucketName    source bucket name.
+// srcObjectKey    source object name.
+// destObjectKey    target object name. The target bucket name is Bucket.BucketName.
+// options    copy options. Check out parameter options in function CopyObject.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) CopyObjectFrom(srcBucketName, srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) {
+       destBucketName := bucket.BucketName
+       var out CopyObjectResult
+       srcBucket, err := bucket.Client.Bucket(srcBucketName)
+       if err != nil {
+               return out, err
+       }
+
+       return srcBucket.copy(srcObjectKey, destBucketName, destObjectKey, options...)
+}
+
+func (bucket Bucket) copy(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) {
+       var out CopyObjectResult
+
+       //first find version id
+       versionIdKey := "versionId"
+       versionId, _ := FindOption(options, versionIdKey, nil)
+       if versionId == nil {
+               options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey)))
+       } else {
+               options = DeleteOption(options, versionIdKey)
+               options = append(options, CopySourceVersion(bucket.BucketName, url.QueryEscape(srcObjectKey), versionId.(string)))
+       }
+
+       headers := make(map[string]string)
+       err := handleOptions(headers, options)
+       if err != nil {
+               return out, err
+       }
+       params := map[string]interface{}{}
+       resp, err := bucket.Client.Conn.Do("PUT", destBucketName, destObjectKey, params, headers, nil, 0, nil)
+
+       // get response header
+       respHeader, _ := FindOption(options, responseHeader, nil)
+       if respHeader != nil {
+               pRespHeader := respHeader.(*http.Header)
+               *pRespHeader = resp.Headers
+       }
+
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// AppendObject uploads the data in the way of appending an existing or new object.
+//
+// AppendObject the parameter appendPosition specifies which postion (in the target object) to append. For the first append (to a non-existing file),
+// the appendPosition should be 0. The appendPosition in the subsequent calls will be the current object length.
+// For example, the first appendObject's appendPosition is 0 and it uploaded 65536 bytes data, then the second call's position is 65536.
+// The response header x-oss-next-append-position after each successful request also specifies the next call's append position (so the caller need not to maintain this information).
+//
+// objectKey    the target object to append to.
+// reader    io.Reader. The read instance for reading the data to append.
+// appendPosition    the start position to append.
+// destObjectProperties    the options for the first appending, such as CacheControl, ContentDisposition, ContentEncoding,
+//                         Expires, ServerSideEncryption, ObjectACL.
+//
+// int64    the next append position, it's valid when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) AppendObject(objectKey string, reader io.Reader, appendPosition int64, options ...Option) (int64, error) {
+       request := &AppendObjectRequest{
+               ObjectKey: objectKey,
+               Reader:    reader,
+               Position:  appendPosition,
+       }
+
+       result, err := bucket.DoAppendObject(request, options)
+       if err != nil {
+               return appendPosition, err
+       }
+
+       return result.NextPosition, err
+}
+
+// DoAppendObject is the actual API that does the object append.
+//
+// request    the request object for appending object.
+// options    the options for appending object.
+//
+// AppendObjectResult    the result object for appending object.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) DoAppendObject(request *AppendObjectRequest, options []Option) (*AppendObjectResult, error) {
+       params := map[string]interface{}{}
+       params["append"] = nil
+       params["position"] = strconv.FormatInt(request.Position, 10)
+       headers := make(map[string]string)
+
+       opts := AddContentType(options, request.ObjectKey)
+       handleOptions(headers, opts)
+
+       var initCRC uint64
+       isCRCSet, initCRCOpt, _ := IsOptionSet(options, initCRC64)
+       if isCRCSet {
+               initCRC = initCRCOpt.(uint64)
+       }
+
+       listener := GetProgressListener(options)
+
+       handleOptions(headers, opts)
+       resp, err := bucket.Client.Conn.Do("POST", bucket.BucketName, request.ObjectKey, params, headers,
+               request.Reader, initCRC, listener)
+
+       // get response header
+       respHeader, _ := FindOption(options, responseHeader, nil)
+       if respHeader != nil {
+               pRespHeader := respHeader.(*http.Header)
+               *pRespHeader = resp.Headers
+       }
+
+       if err != nil {
+               return nil, err
+       }
+       defer resp.Body.Close()
+
+       nextPosition, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderOssNextAppendPosition), 10, 64)
+       result := &AppendObjectResult{
+               NextPosition: nextPosition,
+               CRC:          resp.ServerCRC,
+       }
+
+       if bucket.GetConfig().IsEnableCRC && isCRCSet {
+               err = CheckCRC(resp, "AppendObject")
+               if err != nil {
+                       return result, err
+               }
+       }
+
+       return result, nil
+}
+
+// DeleteObject deletes the object.
+//
+// objectKey    the object key to delete.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) DeleteObject(objectKey string, options ...Option) error {
+       params, _ := GetRawParams(options)
+       resp, err := bucket.do("DELETE", objectKey, params, options, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// DeleteObjects deletes multiple objects.
+//
+// objectKeys    the object keys to delete.
+// options    the options for deleting objects.
+//            Supported option is DeleteObjectsQuiet which means it will not return error even deletion failed (not recommended). By default it's not used.
+//
+// DeleteObjectsResult    the result object.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) DeleteObjects(objectKeys []string, options ...Option) (DeleteObjectsResult, error) {
+       out := DeleteObjectsResult{}
+       dxml := deleteXML{}
+       for _, key := range objectKeys {
+               dxml.Objects = append(dxml.Objects, DeleteObject{Key: key})
+       }
+
+       isQuiet, _ := FindOption(options, deleteObjectsQuiet, false)
+       dxml.Quiet = isQuiet.(bool)
+
+       bs, err := xml.Marshal(dxml)
+       if err != nil {
+               return out, err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       options = append(options, ContentType(contentType))
+       sum := md5.Sum(bs)
+       b64 := base64.StdEncoding.EncodeToString(sum[:])
+       options = append(options, ContentMD5(b64))
+
+       params := map[string]interface{}{}
+       params["delete"] = nil
+       params["encoding-type"] = "url"
+
+       resp, err := bucket.do("POST", "", params, options, buffer, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       deletedResult := DeleteObjectVersionsResult{}
+       if !dxml.Quiet {
+               if err = xmlUnmarshal(resp.Body, &deletedResult); err == nil {
+                       err = decodeDeleteObjectsResult(&deletedResult)
+               }
+       }
+
+       // Keep compatibility:need convert to struct DeleteObjectsResult
+       out.XMLName = deletedResult.XMLName
+       for _, v := range deletedResult.DeletedObjectsDetail {
+               out.DeletedObjects = append(out.DeletedObjects, v.Key)
+       }
+
+       return out, err
+}
+
+// DeleteObjectVersions deletes multiple object versions.
+//
+// objectVersions    the object keys and versions to delete.
+// options    the options for deleting objects.
+//            Supported option is DeleteObjectsQuiet which means it will not return error even deletion failed (not recommended). By default it's not used.
+//
+// DeleteObjectVersionsResult    the result object.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) DeleteObjectVersions(objectVersions []DeleteObject, options ...Option) (DeleteObjectVersionsResult, error) {
+       out := DeleteObjectVersionsResult{}
+       dxml := deleteXML{}
+       dxml.Objects = objectVersions
+
+       isQuiet, _ := FindOption(options, deleteObjectsQuiet, false)
+       dxml.Quiet = isQuiet.(bool)
+
+       bs, err := xml.Marshal(dxml)
+       if err != nil {
+               return out, err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       options = append(options, ContentType(contentType))
+       sum := md5.Sum(bs)
+       b64 := base64.StdEncoding.EncodeToString(sum[:])
+       options = append(options, ContentMD5(b64))
+
+       params := map[string]interface{}{}
+       params["delete"] = nil
+       params["encoding-type"] = "url"
+
+       resp, err := bucket.do("POST", "", params, options, buffer, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       if !dxml.Quiet {
+               if err = xmlUnmarshal(resp.Body, &out); err == nil {
+                       err = decodeDeleteObjectsResult(&out)
+               }
+       }
+       return out, err
+}
+
+// IsObjectExist checks if the object exists.
+//
+// bool    flag of object's existence (true:exists; false:non-exist) when error is nil.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) IsObjectExist(objectKey string, options ...Option) (bool, error) {
+       _, err := bucket.GetObjectMeta(objectKey, options...)
+       if err == nil {
+               return true, nil
+       }
+
+       switch err.(type) {
+       case ServiceError:
+               if err.(ServiceError).StatusCode == 404 {
+                       return false, nil
+               }
+       }
+
+       return false, err
+}
+
+// ListObjects lists the objects under the current bucket.
+//
+// options    it contains all the filters for listing objects.
+//            It could specify a prefix filter on object keys,  the max keys count to return and the object key marker and the delimiter for grouping object names.
+//            The key marker means the returned objects' key must be greater than it in lexicographic order.
+//
+//            For example, if the bucket has 8 objects, my-object-1, my-object-11, my-object-2, my-object-21,
+//            my-object-22, my-object-3, my-object-31, my-object-32. If the prefix is my-object-2 (no other filters), then it returns
+//            my-object-2, my-object-21, my-object-22 three objects. If the marker is my-object-22 (no other filters), then it returns
+//            my-object-3, my-object-31, my-object-32 three objects. If the max keys is 5, then it returns 5 objects.
+//            The three filters could be used together to achieve filter and paging functionality.
+//            If the prefix is the folder name, then it could list all files under this folder (including the files under its subfolders).
+//            But if the delimiter is specified with '/', then it only returns that folder's files (no subfolder's files). The direct subfolders are in the commonPrefixes properties.
+//            For example, if the bucket has three objects fun/test.jpg, fun/movie/001.avi, fun/movie/007.avi. And if the prefix is "fun/", then it returns all three objects.
+//            But if the delimiter is '/', then only "fun/test.jpg" is returned as files and fun/movie/ is returned as common prefix.
+//
+//            For common usage scenario, check out sample/list_object.go.
+//
+// ListObjectsResult    the return value after operation succeeds (only valid when error is nil).
+//
+func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) {
+       var out ListObjectsResult
+
+       options = append(options, EncodingType("url"))
+       params, err := GetRawParams(options)
+       if err != nil {
+               return out, err
+       }
+
+       resp, err := bucket.do("GET", "", params, options, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       if err != nil {
+               return out, err
+       }
+
+       err = decodeListObjectsResult(&out)
+       return out, err
+}
+
+// Recommend to use ListObjectsV2 to replace ListObjects
+// ListOListObjectsV2bjects lists the objects under the current bucket.
+// ListObjectsResultV2    the return value after operation succeeds (only valid when error is nil).
+func (bucket Bucket) ListObjectsV2(options ...Option) (ListObjectsResultV2, error) {
+       var out ListObjectsResultV2
+
+       options = append(options, EncodingType("url"))
+       options = append(options, ListType(2))
+       params, err := GetRawParams(options)
+       if err != nil {
+               return out, err
+       }
+
+       resp, err := bucket.do("GET", "", params, options, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       if err != nil {
+               return out, err
+       }
+
+       err = decodeListObjectsResultV2(&out)
+       return out, err
+}
+
+// ListObjectVersions lists objects of all versions under the current bucket.
+func (bucket Bucket) ListObjectVersions(options ...Option) (ListObjectVersionsResult, error) {
+       var out ListObjectVersionsResult
+
+       options = append(options, EncodingType("url"))
+       params, err := GetRawParams(options)
+       if err != nil {
+               return out, err
+       }
+       params["versions"] = nil
+
+       resp, err := bucket.do("GET", "", params, options, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       if err != nil {
+               return out, err
+       }
+
+       err = decodeListObjectVersionsResult(&out)
+       return out, err
+}
+
+// SetObjectMeta sets the metadata of the Object.
+//
+// objectKey    object
+// options    options for setting the metadata. The valid options are CacheControl, ContentDisposition, ContentEncoding, Expires,
+//            ServerSideEncryption, and custom metadata.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) SetObjectMeta(objectKey string, options ...Option) error {
+       options = append(options, MetadataDirective(MetaReplace))
+       _, err := bucket.CopyObject(objectKey, objectKey, options...)
+       return err
+}
+
+// GetObjectDetailedMeta gets the object's detailed metadata
+//
+// objectKey    object key.
+// options    the constraints of the object. Only when the object meets the requirements this method will return the metadata. Otherwise returns error. Valid options are IfModifiedSince, IfUnmodifiedSince,
+//            IfMatch, IfNoneMatch. For more details check out https://help.aliyun.com/document_detail/oss/api-reference/object/HeadObject.html
+//
+// http.Header    object meta when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option) (http.Header, error) {
+       params, _ := GetRawParams(options)
+       resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil)
+       if err != nil {
+               return nil, err
+       }
+       defer resp.Body.Close()
+
+       return resp.Headers, nil
+}
+
+// GetObjectMeta gets object metadata.
+//
+// GetObjectMeta is more lightweight than GetObjectDetailedMeta as it only returns basic metadata including ETag
+// size, LastModified. The size information is in the HTTP header Content-Length.
+//
+// objectKey    object key
+//
+// http.Header    the object's metadata, valid when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) GetObjectMeta(objectKey string, options ...Option) (http.Header, error) {
+       params, _ := GetRawParams(options)
+       params["objectMeta"] = nil
+       //resp, err := bucket.do("GET", objectKey, "?objectMeta", "", nil, nil, nil)
+       resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil)
+       if err != nil {
+               return nil, err
+       }
+       defer resp.Body.Close()
+
+       return resp.Headers, nil
+}
+
+// SetObjectACL updates the object's ACL.
+//
+// Only the bucket's owner could update object's ACL which priority is higher than bucket's ACL.
+// For example, if the bucket ACL is private and object's ACL is public-read-write.
+// Then object's ACL is used and it means all users could read or write that object.
+// When the object's ACL is not set, then bucket's ACL is used as the object's ACL.
+//
+// Object read operations include GetObject, HeadObject, CopyObject and UploadPartCopy on the source object;
+// Object write operations include PutObject, PostObject, AppendObject, DeleteObject, DeleteMultipleObjects,
+// CompleteMultipartUpload and CopyObject on target object.
+//
+// objectKey    the target object key (to set the ACL on)
+// objectAcl    object ACL. Valid options are PrivateACL, PublicReadACL, PublicReadWriteACL.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType, options ...Option) error {
+       options = append(options, ObjectACL(objectACL))
+       params, _ := GetRawParams(options)
+       params["acl"] = nil
+       resp, err := bucket.do("PUT", objectKey, params, options, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetObjectACL gets object's ACL
+//
+// objectKey    the object to get ACL from.
+//
+// GetObjectACLResult    the result object when error is nil. GetObjectACLResult.Acl is the object ACL.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) GetObjectACL(objectKey string, options ...Option) (GetObjectACLResult, error) {
+       var out GetObjectACLResult
+       params, _ := GetRawParams(options)
+       params["acl"] = nil
+       resp, err := bucket.do("GET", objectKey, params, options, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// PutSymlink creates a symlink (to point to an existing object)
+//
+// Symlink cannot point to another symlink.
+// When creating a symlink, it does not check the existence of the target file, and does not check if the target file is symlink.
+// Neither it checks the caller's permission on the target file. All these checks are deferred to the actual GetObject call via this symlink.
+// If trying to add an existing file, as long as the caller has the write permission, the existing one will be overwritten.
+// If the x-oss-meta- is specified, it will be added as the metadata of the symlink file.
+//
+// symObjectKey    the symlink object's key.
+// targetObjectKey    the target object key to point to.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) PutSymlink(symObjectKey string, targetObjectKey string, options ...Option) error {
+       options = append(options, symlinkTarget(url.QueryEscape(targetObjectKey)))
+       params, _ := GetRawParams(options)
+       params["symlink"] = nil
+       resp, err := bucket.do("PUT", symObjectKey, params, options, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetSymlink gets the symlink object with the specified key.
+// If the symlink object does not exist, returns 404.
+//
+// objectKey    the symlink object's key.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//          When error is nil, the target file key is in the X-Oss-Symlink-Target header of the returned object.
+//
+func (bucket Bucket) GetSymlink(objectKey string, options ...Option) (http.Header, error) {
+       params, _ := GetRawParams(options)
+       params["symlink"] = nil
+       resp, err := bucket.do("GET", objectKey, params, options, nil, nil)
+       if err != nil {
+               return nil, err
+       }
+       defer resp.Body.Close()
+
+       targetObjectKey := resp.Headers.Get(HTTPHeaderOssSymlinkTarget)
+       targetObjectKey, err = url.QueryUnescape(targetObjectKey)
+       if err != nil {
+               return resp.Headers, err
+       }
+       resp.Headers.Set(HTTPHeaderOssSymlinkTarget, targetObjectKey)
+       return resp.Headers, err
+}
+
+// RestoreObject restores the object from the archive storage.
+//
+// An archive object is in cold status by default and it cannot be accessed.
+// When restore is called on the cold object, it will become available for access after some time.
+// If multiple restores are called on the same file when the object is being restored, server side does nothing for additional calls but returns success.
+// By default, the restored object is available for access for one day. After that it will be unavailable again.
+// But if another RestoreObject are called after the file is restored, then it will extend one day's access time of that object, up to 7 days.
+//
+// objectKey    object key to restore.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) RestoreObject(objectKey string, options ...Option) error {
+       params, _ := GetRawParams(options)
+       params["restore"] = nil
+       resp, err := bucket.do("POST", objectKey, params, options, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK, http.StatusAccepted})
+}
+
+// RestoreObjectDetail support more features than RestoreObject
+func (bucket Bucket) RestoreObjectDetail(objectKey string, restoreConfig RestoreConfiguration, options ...Option) error {
+       if restoreConfig.Tier == "" {
+               // Expedited, Standard, Bulk
+               restoreConfig.Tier = string(RestoreStandard)
+       }
+
+       if restoreConfig.Days == 0 {
+               restoreConfig.Days = 1
+       }
+
+       bs, err := xml.Marshal(restoreConfig)
+       if err != nil {
+               return err
+       }
+
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       options = append(options, ContentType(contentType))
+
+       params, _ := GetRawParams(options)
+       params["restore"] = nil
+
+       resp, err := bucket.do("POST", objectKey, params, options, buffer, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK, http.StatusAccepted})
+}
+
+// RestoreObjectXML support more features than RestoreObject
+func (bucket Bucket) RestoreObjectXML(objectKey, configXML string, options ...Option) error {
+       buffer := new(bytes.Buffer)
+       buffer.Write([]byte(configXML))
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       options = append(options, ContentType(contentType))
+
+       params, _ := GetRawParams(options)
+       params["restore"] = nil
+
+       resp, err := bucket.do("POST", objectKey, params, options, buffer, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK, http.StatusAccepted})
+}
+
+// SignURL signs the URL. Users could access the object directly with this URL without getting the AK.
+//
+// objectKey    the target object to sign.
+// signURLConfig    the configuration for the signed URL
+//
+// string    returns the signed URL, when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) SignURL(objectKey string, method HTTPMethod, expiredInSec int64, options ...Option) (string, error) {
+       if expiredInSec < 0 {
+               return "", fmt.Errorf("invalid expires: %d, expires must bigger than 0", expiredInSec)
+       }
+       expiration := time.Now().Unix() + expiredInSec
+
+       params, err := GetRawParams(options)
+       if err != nil {
+               return "", err
+       }
+
+       headers := make(map[string]string)
+       err = handleOptions(headers, options)
+       if err != nil {
+               return "", err
+       }
+
+       return bucket.Client.Conn.signURL(method, bucket.BucketName, objectKey, expiration, params, headers), nil
+}
+
+// PutObjectWithURL uploads an object with the URL. If the object exists, it will be overwritten.
+// PutObjectWithURL It will not generate minetype according to the key name.
+//
+// signedURL    signed URL.
+// reader    io.Reader the read instance for reading the data for the upload.
+// options    the options for uploading the data. The valid options are CacheControl, ContentDisposition, ContentEncoding,
+//            Expires, ServerSideEncryption, ObjectACL and custom metadata. Check out the following link for details:
+//            https://help.aliyun.com/document_detail/oss/api-reference/object/PutObject.html
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) PutObjectWithURL(signedURL string, reader io.Reader, options ...Option) error {
+       resp, err := bucket.DoPutObjectWithURL(signedURL, reader, options)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return err
+}
+
+// PutObjectFromFileWithURL uploads an object from a local file with the signed URL.
+// PutObjectFromFileWithURL It does not generate mimetype according to object key's name or the local file name.
+//
+// signedURL    the signed URL.
+// filePath    local file path, such as dirfile.txt, for uploading.
+// options    options for uploading, same as the options in PutObject function.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) PutObjectFromFileWithURL(signedURL, filePath string, options ...Option) error {
+       fd, err := os.Open(filePath)
+       if err != nil {
+               return err
+       }
+       defer fd.Close()
+
+       resp, err := bucket.DoPutObjectWithURL(signedURL, fd, options)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return err
+}
+
+// DoPutObjectWithURL is the actual API that does the upload with URL work(internal for SDK)
+//
+// signedURL    the signed URL.
+// reader    io.Reader the read instance for getting the data to upload.
+// options    options for uploading.
+//
+// Response    the response object which contains the HTTP response.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) DoPutObjectWithURL(signedURL string, reader io.Reader, options []Option) (*Response, error) {
+       listener := GetProgressListener(options)
+
+       params := map[string]interface{}{}
+       resp, err := bucket.doURL("PUT", signedURL, params, options, reader, listener)
+       if err != nil {
+               return nil, err
+       }
+
+       if bucket.GetConfig().IsEnableCRC {
+               err = CheckCRC(resp, "DoPutObjectWithURL")
+               if err != nil {
+                       return resp, err
+               }
+       }
+
+       err = CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+
+       return resp, err
+}
+
+// GetObjectWithURL downloads the object and returns the reader instance,  with the signed URL.
+//
+// signedURL    the signed URL.
+// options    options for downloading the object. Valid options are IfModifiedSince, IfUnmodifiedSince, IfMatch,
+//            IfNoneMatch, AcceptEncoding. For more information, check out the following link:
+//            https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html
+//
+// io.ReadCloser    the reader object for getting the data from response. It needs be closed after the usage. It's only valid when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) GetObjectWithURL(signedURL string, options ...Option) (io.ReadCloser, error) {
+       result, err := bucket.DoGetObjectWithURL(signedURL, options)
+       if err != nil {
+               return nil, err
+       }
+       return result.Response, nil
+}
+
+// GetObjectToFileWithURL downloads the object into a local file with the signed URL.
+//
+// signedURL    the signed URL
+// filePath    the local file path to download to.
+// options    the options for downloading object. Check out the parameter options in function GetObject for the reference.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) GetObjectToFileWithURL(signedURL, filePath string, options ...Option) error {
+       tempFilePath := filePath + TempFileSuffix
+
+       // Get the object's content
+       result, err := bucket.DoGetObjectWithURL(signedURL, options)
+       if err != nil {
+               return err
+       }
+       defer result.Response.Close()
+
+       // If the file does not exist, create one. If exists, then overwrite it.
+       fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, FilePermMode)
+       if err != nil {
+               return err
+       }
+
+       // Save the data to the file.
+       _, err = io.Copy(fd, result.Response.Body)
+       fd.Close()
+       if err != nil {
+               return err
+       }
+
+       // Compare the CRC value. If CRC values do not match, return error.
+       hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange)
+       encodeOpt, _ := FindOption(options, HTTPHeaderAcceptEncoding, nil)
+       acceptEncoding := ""
+       if encodeOpt != nil {
+               acceptEncoding = encodeOpt.(string)
+       }
+
+       if bucket.GetConfig().IsEnableCRC && !hasRange && acceptEncoding != "gzip" {
+               result.Response.ClientCRC = result.ClientCRC.Sum64()
+               err = CheckCRC(result.Response, "GetObjectToFileWithURL")
+               if err != nil {
+                       os.Remove(tempFilePath)
+                       return err
+               }
+       }
+
+       return os.Rename(tempFilePath, filePath)
+}
+
+// DoGetObjectWithURL is the actual API that downloads the file with the signed URL.
+//
+// signedURL    the signed URL.
+// options    the options for getting object. Check out parameter options in GetObject for the reference.
+//
+// GetObjectResult    the result object when the error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) DoGetObjectWithURL(signedURL string, options []Option) (*GetObjectResult, error) {
+       params, _ := GetRawParams(options)
+       resp, err := bucket.doURL("GET", signedURL, params, options, nil, nil)
+       if err != nil {
+               return nil, err
+       }
+
+       result := &GetObjectResult{
+               Response: resp,
+       }
+
+       // CRC
+       var crcCalc hash.Hash64
+       hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange)
+       if bucket.GetConfig().IsEnableCRC && !hasRange {
+               crcCalc = crc64.New(CrcTable())
+               result.ServerCRC = resp.ServerCRC
+               result.ClientCRC = crcCalc
+       }
+
+       // Progress
+       listener := GetProgressListener(options)
+
+       contentLen, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderContentLength), 10, 64)
+       resp.Body = TeeReader(resp.Body, crcCalc, contentLen, listener, nil)
+
+       return result, nil
+}
+
+//
+// ProcessObject apply process on the specified image file.
+//
+// The supported process includes resize, rotate, crop, watermark, format,
+// udf, customized style, etc.
+//
+//
+// objectKey   object key to process.
+// process     process string, such as "image/resize,w_100|sys/saveas,o_dGVzdC5qcGc,b_dGVzdA"
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) ProcessObject(objectKey string, process string, options ...Option) (ProcessObjectResult, error) {
+       var out ProcessObjectResult
+       params, _ := GetRawParams(options)
+       params["x-oss-process"] = nil
+       processData := fmt.Sprintf("%v=%v", "x-oss-process", process)
+       data := strings.NewReader(processData)
+       resp, err := bucket.do("POST", objectKey, params, nil, data, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = jsonUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+//
+// PutObjectTagging add tagging to object
+//
+// objectKey  object key to add tagging
+// tagging    tagging to be added
+//
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) PutObjectTagging(objectKey string, tagging Tagging, options ...Option) error {
+       bs, err := xml.Marshal(tagging)
+       if err != nil {
+               return err
+       }
+
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       params, _ := GetRawParams(options)
+       params["tagging"] = nil
+       resp, err := bucket.do("PUT", objectKey, params, options, buffer, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return nil
+}
+
+//
+// GetObjectTagging get tagging of the object
+//
+// objectKey  object key to get tagging
+//
+// Tagging
+// error      nil if success, otherwise error
+
+func (bucket Bucket) GetObjectTagging(objectKey string, options ...Option) (GetObjectTaggingResult, error) {
+       var out GetObjectTaggingResult
+       params, _ := GetRawParams(options)
+       params["tagging"] = nil
+
+       resp, err := bucket.do("GET", objectKey, params, options, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+//
+// DeleteObjectTagging delete object taggging
+//
+// objectKey  object key to delete tagging
+//
+// error      nil if success, otherwise error
+//
+func (bucket Bucket) DeleteObjectTagging(objectKey string, options ...Option) error {
+       params, _ := GetRawParams(options)
+       params["tagging"] = nil
+
+       if objectKey == "" {
+               return fmt.Errorf("invalid argument: object name is empty")
+       }
+
+       resp, err := bucket.do("DELETE", objectKey, params, options, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+func (bucket Bucket) OptionsMethod(objectKey string, options ...Option) (http.Header, error) {
+       var out http.Header
+       resp, err := bucket.do("OPTIONS", objectKey, nil, options, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+       out = resp.Headers
+       return out, nil
+}
+
+// public
+func (bucket Bucket) Do(method, objectName string, params map[string]interface{}, options []Option,
+       data io.Reader, listener ProgressListener) (*Response, error) {
+       return bucket.do(method, objectName, params, options, data, listener)
+}
+
+// Private
+func (bucket Bucket) do(method, objectName string, params map[string]interface{}, options []Option,
+       data io.Reader, listener ProgressListener) (*Response, error) {
+       headers := make(map[string]string)
+       err := handleOptions(headers, options)
+       if err != nil {
+               return nil, err
+       }
+
+       err = CheckBucketName(bucket.BucketName)
+       if len(bucket.BucketName) > 0 && err != nil {
+               return nil, err
+       }
+
+       resp, err := bucket.Client.Conn.Do(method, bucket.BucketName, objectName,
+               params, headers, data, 0, listener)
+
+       // get response header
+       respHeader, _ := FindOption(options, responseHeader, nil)
+       if respHeader != nil && resp != nil {
+               pRespHeader := respHeader.(*http.Header)
+               *pRespHeader = resp.Headers
+       }
+
+       return resp, err
+}
+
+func (bucket Bucket) doURL(method HTTPMethod, signedURL string, params map[string]interface{}, options []Option,
+       data io.Reader, listener ProgressListener) (*Response, error) {
+       headers := make(map[string]string)
+       err := handleOptions(headers, options)
+       if err != nil {
+               return nil, err
+       }
+
+       resp, err := bucket.Client.Conn.DoURL(method, signedURL, headers, data, 0, listener)
+
+       // get response header
+       respHeader, _ := FindOption(options, responseHeader, nil)
+       if respHeader != nil {
+               pRespHeader := respHeader.(*http.Header)
+               *pRespHeader = resp.Headers
+       }
+
+       return resp, err
+}
+
+func (bucket Bucket) GetConfig() *Config {
+       return bucket.Client.Config
+}
+
+func AddContentType(options []Option, keys ...string) []Option {
+       typ := TypeByExtension("")
+       for _, key := range keys {
+               typ = TypeByExtension(key)
+               if typ != "" {
+                       break
+               }
+       }
+
+       if typ == "" {
+               typ = "application/octet-stream"
+       }
+
+       opts := []Option{ContentType(typ)}
+       opts = append(opts, options...)
+
+       return opts
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket_credential_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket_credential_test.go
new file mode 100644 (file)
index 0000000..c4c693c
--- /dev/null
@@ -0,0 +1,616 @@
+// Credentials test
+package oss
+
+import (
+       "bytes"
+       "io/ioutil"
+       "math/rand"
+       "os"
+       "strconv"
+       "strings"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssCredentialBucketSuite struct {
+       client    *Client
+       creClient *Client
+       bucket    *Bucket
+       creBucket *Bucket
+}
+
+var _ = Suite(&OssCredentialBucketSuite{})
+
+func (cs *OssCredentialBucketSuite) credentialSubUser(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       err = client.CreateBucket(credentialBucketName)
+       c.Assert(err, IsNil)
+       cs.client = client
+       policyInfo := `
+       {
+               "Version":"1",
+               "Statement":[
+                       {
+                               "Action":[
+                                       "oss:*"
+                               ],
+                               "Effect":"Allow",
+                               "Principal":["` + credentialUID + `"],
+                               "Resource":["acs:oss:*:*:` + credentialBucketName + `", "acs:oss:*:*:` + credentialBucketName + `/*"]
+                       }
+               ]
+       }`
+
+       err = client.SetBucketPolicy(credentialBucketName, policyInfo)
+       c.Assert(err, IsNil)
+
+       bucket, err := cs.client.Bucket(credentialBucketName)
+       c.Assert(err, IsNil)
+       cs.bucket = bucket
+}
+
+// SetUpSuite runs once when the suite starts running.
+func (cs *OssCredentialBucketSuite) SetUpSuite(c *C) {
+       if credentialUID == "" {
+               testLogger.Println("the cerdential UID is NULL, skip the credential test")
+               c.Skip("the credential Uid is null")
+       }
+
+       cs.credentialSubUser(c)
+       client, err := New(endpoint, credentialAccessID, credentialAccessKey)
+       c.Assert(err, IsNil)
+       cs.creClient = client
+
+       bucket, err := cs.creClient.Bucket(credentialBucketName)
+       c.Assert(err, IsNil)
+       cs.creBucket = bucket
+
+       testLogger.Println("test credetial bucket started")
+}
+
+func (cs *OssCredentialBucketSuite) TearDownSuite(c *C) {
+       if credentialUID == "" {
+               c.Skip("the credential Uid is null")
+       }
+       for _, bucket := range []*Bucket{cs.bucket} {
+               // Delete multipart
+               keyMarker := KeyMarker("")
+               uploadIDMarker := UploadIDMarker("")
+               for {
+                       lmu, err := bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+                       c.Assert(err, IsNil)
+                       for _, upload := range lmu.Uploads {
+                               imur := InitiateMultipartUploadResult{Bucket: credentialBucketName, Key: upload.Key, UploadID: upload.UploadID}
+                               err = bucket.AbortMultipartUpload(imur)
+                               c.Assert(err, IsNil)
+                       }
+                       keyMarker = KeyMarker(lmu.NextKeyMarker)
+                       uploadIDMarker = UploadIDMarker(lmu.NextUploadIDMarker)
+                       if !lmu.IsTruncated {
+                               break
+                       }
+               }
+               // Delete objects
+               marker := Marker("")
+               for {
+                       lor, err := bucket.ListObjects(marker)
+                       c.Assert(err, IsNil)
+                       for _, object := range lor.Objects {
+                               err = bucket.DeleteObject(object.Key)
+                               c.Assert(err, IsNil)
+                       }
+                       marker = Marker(lor.NextMarker)
+                       if !lor.IsTruncated {
+                               break
+                       }
+               }
+       }
+       err := cs.client.DeleteBucket(credentialBucketName)
+       c.Assert(err, IsNil)
+       testLogger.Println("test credential bucket completed")
+}
+
+// Test put/get/list/delte object
+func (cs *OssCredentialBucketSuite) TestReqerPaymentNoRequester(c *C) {
+       // Set bucket is requester who send the request
+       reqPayConf := RequestPaymentConfiguration{
+               Payer: string(Requester),
+       }
+       err := cs.client.SetBucketRequestPayment(credentialBucketName, reqPayConf)
+       c.Assert(err, IsNil)
+
+       key := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(18)
+
+       // Put object
+       err = cs.creBucket.PutObject(key, strings.NewReader(objectValue))
+       c.Assert(err, NotNil)
+
+       // Get object
+       _, err = cs.creBucket.GetObject(key)
+       c.Assert(err, NotNil)
+
+       // List object
+       _, err = cs.creBucket.ListObjects()
+       c.Assert(err, NotNil)
+
+       err = cs.creBucket.DeleteObject(key)
+       c.Assert(err, NotNil)
+
+       // Set bucket is BucketOwner
+       reqPayConf.Payer = string(BucketOwner)
+       err = cs.client.SetBucketRequestPayment(credentialBucketName, reqPayConf)
+       c.Assert(err, IsNil)
+}
+
+// Test put/get/list/delte object
+func (cs *OssCredentialBucketSuite) TestReqerPaymentWithRequester(c *C) {
+       // Set bucket is requester who send the request
+       reqPayConf := RequestPaymentConfiguration{
+               Payer: string(Requester),
+       }
+       err := cs.client.SetBucketRequestPayment(credentialBucketName, reqPayConf)
+       c.Assert(err, IsNil)
+
+       key := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(18)
+
+       // Put object with a bucketowner
+       err = cs.creBucket.PutObject(key, strings.NewReader(objectValue), RequestPayer(BucketOwner))
+       c.Assert(err, NotNil)
+
+       // Put object
+       err = cs.creBucket.PutObject(key, strings.NewReader(objectValue), RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Get object
+       body, err := cs.creBucket.GetObject(key, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       defer body.Close()
+
+       data, err := ioutil.ReadAll(body)
+       c.Assert(err, IsNil)
+       c.Assert(string(data), Equals, objectValue)
+
+       // List object
+       lor, err := cs.creBucket.ListObjects(RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 1)
+
+       err = cs.creBucket.DeleteObject(key, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Set bucket is BucketOwner
+       reqPayConf.Payer = string(BucketOwner)
+       err = cs.client.SetBucketRequestPayment(credentialBucketName, reqPayConf)
+       c.Assert(err, IsNil)
+}
+
+// Test put/get/list/delte object
+func (cs *OssCredentialBucketSuite) TestOwnerPaymentNoRequester(c *C) {
+       // Set bucket is requester who send the request
+       reqPayConf := RequestPaymentConfiguration{
+               Payer: string(BucketOwner),
+       }
+       err := cs.client.SetBucketRequestPayment(credentialBucketName, reqPayConf)
+       c.Assert(err, IsNil)
+
+       key := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(18)
+
+       // Put object
+       err = cs.creBucket.PutObject(key, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Get object
+       body, err := cs.creBucket.GetObject(key)
+       c.Assert(err, IsNil)
+       defer body.Close()
+
+       data, err := ioutil.ReadAll(body)
+       c.Assert(err, IsNil)
+       c.Assert(string(data), Equals, objectValue)
+
+       // List object
+       lor, err := cs.creBucket.ListObjects()
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 1)
+
+       err = cs.creBucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+// Test put/get/list/delte object
+func (cs *OssCredentialBucketSuite) TestOwnerPaymentWithRequester(c *C) {
+       // Set bucket is BucketOwner payer
+       reqPayConf := RequestPaymentConfiguration{
+               Payer: string(BucketOwner),
+       }
+
+       err := cs.client.SetBucketRequestPayment(credentialBucketName, reqPayConf)
+       c.Assert(err, IsNil)
+
+       key := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(18)
+
+       // Put object
+       err = cs.creBucket.PutObject(key, strings.NewReader(objectValue), RequestPayer(BucketOwner))
+       c.Assert(err, IsNil)
+
+       // Put object
+       err = cs.creBucket.PutObject(key, strings.NewReader(objectValue), RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Get object
+       body, err := cs.creBucket.GetObject(key, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       defer body.Close()
+
+       data, err := ioutil.ReadAll(body)
+       c.Assert(err, IsNil)
+       c.Assert(string(data), Equals, objectValue)
+
+       // List object
+       lor, err := cs.creBucket.ListObjects(RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 1)
+
+       err = cs.creBucket.DeleteObject(key, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+}
+
+// TestPutObjectFromFile
+func (cs *OssCredentialBucketSuite) TestPutObjectFromFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       localFile := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       // Put
+       err := cs.creBucket.PutObjectFromFile(objectName, localFile, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Check
+       err = cs.creBucket.GetObjectToFile(objectName, newFile, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       eq, err := compareFiles(localFile, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       meta, err := cs.creBucket.GetObjectDetailedMeta(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("Content-Type"), Equals, "image/jpeg")
+
+       acl, err := cs.creBucket.GetObjectACL(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       testLogger.Println("aclRes:", acl)
+       c.Assert(acl.ACL, Equals, "default")
+
+       err = cs.creBucket.DeleteObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Put with properties
+       options := []Option{
+               Expires(futureDate),
+               ObjectACL(ACLPublicRead),
+               Meta("myprop", "mypropval"),
+               RequestPayer(Requester),
+       }
+       err = cs.creBucket.PutObjectFromFile(objectName, localFile, options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       err = cs.creBucket.GetObjectToFile(objectName, newFile, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       eq, err = compareFiles(localFile, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       acl, err = cs.creBucket.GetObjectACL(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectACL:", acl)
+       c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+       meta, err = cs.creBucket.GetObjectDetailedMeta(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       err = cs.creBucket.DeleteObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+}
+
+// TestCopyObject
+func (cs *OssCredentialBucketSuite) TestCopyObject(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(18)
+
+       err := cs.creBucket.PutObject(objectName, strings.NewReader(objectValue),
+               ACL(ACLPublicRead), Meta("my", "myprop"), RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Copy
+       var objectNameDest = objectName + "dest"
+       _, err = cs.creBucket.CopyObject(objectName, objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Check
+       lor, err := cs.creBucket.ListObjects(Prefix(objectName), RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       testLogger.Println("objects:", lor.Objects)
+       c.Assert(len(lor.Objects), Equals, 2)
+
+       body, err := cs.creBucket.GetObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = cs.creBucket.DeleteObject(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Copy with constraints x-oss-copy-source-if-modified-since
+       _, err = cs.creBucket.CopyObject(objectName, objectNameDest, CopySourceIfModifiedSince(futureDate), RequestPayer(Requester))
+       c.Assert(err, NotNil)
+       testLogger.Println("CopyObject:", err)
+
+       // Copy with constraints x-oss-copy-source-if-unmodified-since
+       _, err = cs.creBucket.CopyObject(objectName, objectNameDest, CopySourceIfUnmodifiedSince(futureDate), RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Check
+       lor, err = cs.creBucket.ListObjects(Prefix(objectName), RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       testLogger.Println("objects:", lor.Objects)
+       c.Assert(len(lor.Objects), Equals, 2)
+
+       body, err = cs.creBucket.GetObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = cs.creBucket.DeleteObject(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Copy with constraints x-oss-copy-source-if-match
+       meta, err := cs.creBucket.GetObjectDetailedMeta(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+
+       _, err = cs.creBucket.CopyObject(objectName, objectNameDest, CopySourceIfMatch(meta.Get("Etag")), RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = cs.creBucket.GetObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = cs.creBucket.DeleteObject(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Copy with constraints x-oss-copy-source-if-none-match
+       _, err = cs.creBucket.CopyObject(objectName, objectNameDest, CopySourceIfNoneMatch(meta.Get("Etag")), RequestPayer(Requester))
+       c.Assert(err, NotNil)
+
+       // Copy with constraints x-oss-metadata-directive
+       _, err = cs.creBucket.CopyObject(objectName, objectNameDest, Meta("my", "mydestprop"),
+               MetadataDirective(MetaCopy), RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = cs.creBucket.GetObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       destMeta, err := cs.creBucket.GetObjectDetailedMeta(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+
+       acl, err := cs.creBucket.GetObjectACL(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, "default")
+
+       err = cs.creBucket.DeleteObject(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Copy with constraints x-oss-metadata-directive and self defined dest object meta
+       options := []Option{
+               ObjectACL(ACLPublicReadWrite),
+               Meta("my", "mydestprop"),
+               MetadataDirective(MetaReplace),
+               RequestPayer(Requester),
+       }
+       _, err = cs.creBucket.CopyObject(objectName, objectNameDest, options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = cs.creBucket.GetObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       destMeta, err = cs.creBucket.GetObjectDetailedMeta(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       c.Assert(destMeta.Get("X-Oss-Meta-My"), Equals, "mydestprop")
+
+       acl, err = cs.creBucket.GetObjectACL(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite))
+
+       err = cs.creBucket.DeleteObject(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       err = cs.creBucket.DeleteObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+}
+
+// TestCopyObjectToOrFrom
+func (cs *OssCredentialBucketSuite) TestCopyObjectToOrFrom(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(18)
+       sorBucketName := credentialBucketName + "-sor"
+       objectNameDest := objectName + "-Dest"
+
+       err := cs.client.CreateBucket(sorBucketName)
+       c.Assert(err, IsNil)
+       // Set ACL_PUBLIC_R
+       err = cs.client.SetBucketACL(sorBucketName, ACLPublicRead)
+       c.Assert(err, IsNil)
+
+       sorBucket, err := cs.client.Bucket(sorBucketName)
+       c.Assert(err, IsNil)
+
+       err = sorBucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Copy from
+       _, err = cs.creBucket.CopyObjectFrom(sorBucketName, objectName, objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := cs.creBucket.GetObject(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = cs.creBucket.DeleteObject(objectNameDest, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Copy to
+       _, err = sorBucket.CopyObjectTo(credentialBucketName, objectName, objectName)
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = cs.creBucket.GetObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Clean
+       err = sorBucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       err = cs.creBucket.DeleteObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       err = cs.client.DeleteBucket(sorBucketName)
+       c.Assert(err, IsNil)
+}
+
+// TestAppendObject
+func (cs *OssCredentialBucketSuite) TestAppendObject(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue1 := RandStr(18)
+       objectValue2 := RandStr(18)
+       objectValue := objectValue1 + objectValue2
+       var val = []byte(objectValue)
+       var localFile = RandStr(8) + ".txt"
+       var nextPos int64
+       var midPos = 1 + rand.Intn(len(val)-1)
+
+       var err = CreateFileAndWrite(localFile+"1", val[0:midPos])
+       c.Assert(err, IsNil)
+       err = CreateFileAndWrite(localFile+"2", val[midPos:])
+       c.Assert(err, IsNil)
+
+       // String append
+       nextPos, err = cs.creBucket.AppendObject(objectName, strings.NewReader(objectValue1), nextPos, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       nextPos, err = cs.creBucket.AppendObject(objectName, strings.NewReader(objectValue2), nextPos, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       body, err := cs.creBucket.GetObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = cs.creBucket.DeleteObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // Byte append
+       nextPos = 0
+       nextPos, err = cs.creBucket.AppendObject(objectName, bytes.NewReader(val[0:midPos]), nextPos, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       nextPos, err = cs.creBucket.AppendObject(objectName, bytes.NewReader(val[midPos:]), nextPos, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       body, err = cs.creBucket.GetObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = cs.creBucket.DeleteObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+
+       // File append
+       options := []Option{
+               ObjectACL(ACLPublicReadWrite),
+               Meta("my", "myprop"),
+               RequestPayer(Requester),
+       }
+
+       fd, err := os.Open(localFile + "1")
+       c.Assert(err, IsNil)
+       defer fd.Close()
+       nextPos = 0
+       nextPos, err = cs.creBucket.AppendObject(objectName, fd, nextPos, options...)
+       c.Assert(err, IsNil)
+
+       meta, err := cs.creBucket.GetObjectDetailedMeta(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta, ",", nextPos)
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Appendable")
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("x-oss-Meta-Mine"), Equals, "")
+       c.Assert(meta.Get("X-Oss-Next-Append-Position"), Equals, strconv.FormatInt(nextPos, 10))
+
+       acl, err := cs.creBucket.GetObjectACL(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectACL:", acl)
+       c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite))
+
+       // Second append
+       options = []Option{
+               ObjectACL(ACLPublicRead),
+               Meta("my", "myproptwo"),
+               Meta("mine", "mypropmine"),
+               RequestPayer(Requester),
+       }
+       fd, err = os.Open(localFile + "2")
+       c.Assert(err, IsNil)
+       defer fd.Close()
+       nextPos, err = cs.creBucket.AppendObject(objectName, fd, nextPos, options...)
+       c.Assert(err, IsNil)
+
+       body, err = cs.creBucket.GetObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       meta, err = cs.creBucket.GetObjectDetailedMeta(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta xxx:", meta)
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Appendable")
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("x-Oss-Meta-Mine"), Equals, "")
+       c.Assert(meta.Get("X-Oss-Next-Append-Position"), Equals, strconv.FormatInt(nextPos, 10))
+
+       acl, err = cs.creBucket.GetObjectACL(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+       err = cs.creBucket.DeleteObject(objectName, RequestPayer(Requester))
+       c.Assert(err, IsNil)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket_test.go
new file mode 100644 (file)
index 0000000..d7b2a76
--- /dev/null
@@ -0,0 +1,5313 @@
+package oss
+
+import (
+       "bytes"
+       "encoding/base64"
+       "errors"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "math/rand"
+       "net/http"
+       "net/url"
+       "os"
+       "path/filepath"
+       "strconv"
+       "strings"
+       "time"
+
+       "github.com/baiyubin/aliyun-sts-go-sdk/sts"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssBucketSuite struct {
+       client        *Client
+       bucket        *Bucket
+       archiveBucket *Bucket
+}
+
+var _ = Suite(&OssBucketSuite{})
+
+var (
+       pastDate   = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
+       futureDate = time.Date(2049, time.January, 10, 23, 0, 0, 0, time.UTC)
+)
+
+// SetUpSuite runs once when the suite starts running.
+func (s *OssBucketSuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       s.client = client
+
+       s.client.CreateBucket(bucketName)
+
+       err = s.client.CreateBucket(archiveBucketName, StorageClass(StorageArchive))
+       c.Assert(err, IsNil)
+
+       bucket, err := s.client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       s.bucket = bucket
+
+       archiveBucket, err := s.client.Bucket(archiveBucketName)
+       c.Assert(err, IsNil)
+       s.archiveBucket = archiveBucket
+
+       testLogger.Println("test bucket started")
+}
+
+// TearDownSuite runs before each test or benchmark starts running.
+func (s *OssBucketSuite) TearDownSuite(c *C) {
+       for _, bucket := range []*Bucket{s.bucket, s.archiveBucket} {
+               // Delete multipart
+               keyMarker := KeyMarker("")
+               uploadIDMarker := UploadIDMarker("")
+               for {
+                       lmu, err := bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+                       c.Assert(err, IsNil)
+                       for _, upload := range lmu.Uploads {
+                               imur := InitiateMultipartUploadResult{Bucket: bucketName, Key: upload.Key, UploadID: upload.UploadID}
+                               err = bucket.AbortMultipartUpload(imur)
+                               c.Assert(err, IsNil)
+                       }
+                       keyMarker = KeyMarker(lmu.NextKeyMarker)
+                       uploadIDMarker = UploadIDMarker(lmu.NextUploadIDMarker)
+                       if !lmu.IsTruncated {
+                               break
+                       }
+               }
+
+               // Delete objects
+               marker := Marker("")
+               for {
+                       lor, err := bucket.ListObjects(marker)
+                       c.Assert(err, IsNil)
+                       for _, object := range lor.Objects {
+                               err = bucket.DeleteObject(object.Key)
+                               c.Assert(err, IsNil)
+                       }
+                       marker = Marker(lor.NextMarker)
+                       if !lor.IsTruncated {
+                               break
+                       }
+               }
+
+               // Delete bucket
+               err := s.client.DeleteBucket(bucket.BucketName)
+               c.Assert(err, IsNil)
+       }
+
+       testLogger.Println("test bucket completed")
+}
+
+// SetUpTest runs after each test or benchmark runs.
+func (s *OssBucketSuite) SetUpTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+}
+
+// TearDownTest runs once after all tests or benchmarks have finished running.
+func (s *OssBucketSuite) TearDownTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".txt")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".temp")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".txt1")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".txt2")
+       c.Assert(err, IsNil)
+}
+
+// TestPutObject
+func (s *OssBucketSuite) TestPutObjectOnly(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "大江东去,浪淘尽,千古风流人物。 故垒西边,人道是、三国周郎赤壁。 乱石穿空,惊涛拍岸,卷起千堆雪。 江山如画,一时多少豪杰。" +
+               "遥想公谨当年,小乔初嫁了,雄姿英发。 羽扇纶巾,谈笑间、樯橹灰飞烟灭。故国神游,多情应笑我,早生华发,人生如梦,一尊还酹江月。"
+
+       // Put string
+       var respHeader http.Header
+       err := s.bucket.PutObject(objectName, strings.NewReader(objectValue), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       acl, err := s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("aclRes:", acl)
+       c.Assert(acl.ACL, Equals, "default")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Put bytes
+       err = s.bucket.PutObject(objectName, bytes.NewReader([]byte(objectValue)))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Put file
+       err = CreateFileAndWrite(objectName+".txt", []byte(objectValue))
+       c.Assert(err, IsNil)
+       fd, err := os.Open(objectName + ".txt")
+       c.Assert(err, IsNil)
+
+       err = s.bucket.PutObject(objectName, fd)
+       c.Assert(err, IsNil)
+       os.Remove(objectName + ".txt")
+
+       // Check
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Put with properties
+       objectName = objectNamePrefix + RandStr(8)
+       options := []Option{
+               Expires(futureDate),
+               ObjectACL(ACLPublicRead),
+               Meta("myprop", "mypropval"),
+       }
+       err = s.bucket.PutObject(objectName, strings.NewReader(objectValue), options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       acl, err = s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectACL:", acl)
+       c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssBucketSuite) SignURLTestFunc(c *C, authVersion AuthVersionType, extraHeaders []string) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(20)
+
+       filePath := RandLowStr(10)
+       content := "复写object"
+       CreateFile(filePath, content, c)
+
+       notExistfilePath := RandLowStr(10)
+       os.Remove(notExistfilePath)
+
+       oldType := s.bucket.Client.Config.AuthVersion
+       oldHeaders := s.bucket.Client.Config.AdditionalHeaders
+
+       s.bucket.Client.Config.AuthVersion = authVersion
+       s.bucket.Client.Config.AdditionalHeaders = extraHeaders
+
+       // Sign URL for put
+       str, err := s.bucket.SignURL(objectName, HTTPPut, 60)
+       c.Assert(err, IsNil)
+
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Error put object with URL
+       err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue), ContentType("image/tiff"))
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch")
+
+       err = s.bucket.PutObjectFromFileWithURL(str, filePath, ContentType("image/tiff"))
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch")
+
+       // Put object with URL
+       err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       acl, err := s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, "default")
+
+       // Get object meta
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get(HTTPHeaderContentType), Equals, "application/octet-stream")
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "")
+
+       // Sign URL for function GetObjectWithURL
+       str, err = s.bucket.SignURL(objectName, HTTPGet, 60)
+       c.Assert(err, IsNil)
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Get object with URL
+       body, err := s.bucket.GetObjectWithURL(str)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Sign URL for function PutObjectWithURL
+       options := []Option{
+               ObjectACL(ACLPublicRead),
+               Meta("myprop", "mypropval"),
+               ContentType("image/tiff"),
+               ResponseContentEncoding("deflate"),
+       }
+       str, err = s.bucket.SignURL(objectName, HTTPPut, 60, options...)
+       c.Assert(err, IsNil)
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Put object with URL from file
+       // Without option, error
+       err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue))
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch")
+
+       err = s.bucket.PutObjectFromFileWithURL(str, filePath)
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch")
+
+       // With option, error file
+       err = s.bucket.PutObjectFromFileWithURL(str, notExistfilePath, options...)
+       c.Assert(err, NotNil)
+
+       // With option
+       err = s.bucket.PutObjectFromFileWithURL(str, filePath, options...)
+       c.Assert(err, IsNil)
+
+       // Get object meta
+       meta, err = s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+       c.Assert(meta.Get(HTTPHeaderContentType), Equals, "image/tiff")
+
+       acl, err = s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+       // Sign URL for function GetObjectToFileWithURL
+       str, err = s.bucket.SignURL(objectName, HTTPGet, 60)
+       c.Assert(err, IsNil)
+
+       // Get object to file with URL
+       newFile := RandStr(10)
+       err = s.bucket.GetObjectToFileWithURL(str, newFile)
+       c.Assert(err, IsNil)
+       eq, err := compareFiles(filePath, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+       os.Remove(newFile)
+
+       // Get object to file error
+       err = s.bucket.GetObjectToFileWithURL(str, newFile, options...)
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch")
+       _, err = os.Stat(newFile)
+       c.Assert(err, NotNil)
+
+       // Get object error
+       body, err = s.bucket.GetObjectWithURL(str, options...)
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch")
+       c.Assert(body, IsNil)
+
+       // Sign URL for function GetObjectToFileWithURL
+       options = []Option{
+               Expires(futureDate),
+               ObjectACL(ACLPublicRead),
+               Meta("myprop", "mypropval"),
+               ContentType("image/tiff"),
+               ResponseContentEncoding("deflate"),
+       }
+       str, err = s.bucket.SignURL(objectName, HTTPGet, 60, options...)
+       c.Assert(err, IsNil)
+
+       // Get object to file with URL and options
+       err = s.bucket.GetObjectToFileWithURL(str, newFile, options...)
+       c.Assert(err, IsNil)
+       eq, err = compareFiles(filePath, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+       os.Remove(newFile)
+
+       // Get object to file error
+       err = s.bucket.GetObjectToFileWithURL(str, newFile)
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch")
+       _, err = os.Stat(newFile)
+       c.Assert(err, NotNil)
+
+       // Get object error
+       body, err = s.bucket.GetObjectWithURL(str)
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch")
+       c.Assert(body, IsNil)
+
+       err = s.bucket.PutObjectFromFile(objectName, "../sample/The Go Programming Language.html")
+       c.Assert(err, IsNil)
+       str, err = s.bucket.SignURL(objectName, HTTPGet, 3600, AcceptEncoding("gzip"))
+       c.Assert(err, IsNil)
+       s.bucket.GetObjectToFileWithURL(str, newFile)
+       c.Assert(err, IsNil)
+
+       os.Remove(filePath)
+       os.Remove(newFile)
+
+       // Sign URL error
+       str, err = s.bucket.SignURL(objectName, HTTPGet, -1)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Invalid URL parse
+       str = RandStr(20)
+
+       err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.GetObjectToFileWithURL(str, newFile)
+       c.Assert(err, NotNil)
+
+       s.bucket.Client.Config.AuthVersion = oldType
+       s.bucket.Client.Config.AdditionalHeaders = oldHeaders
+}
+
+func (s *OssBucketSuite) TestSignURL(c *C) {
+       s.SignURLTestFunc(c, AuthV1, []string{})
+       s.SignURLTestFunc(c, AuthV2, []string{})
+       s.SignURLTestFunc(c, AuthV2, []string{"host", "range", "user-agent"})
+}
+
+func (s *OssBucketSuite) SignURLWithEscapedKeyTestFunc(c *C, authVersion AuthVersionType, extraHeaders []string) {
+       // Key with '/'
+       objectName := "zyimg/86/e8/653b5dc97bb0022051a84c632bc4"
+       objectValue := "弃我去者,昨日之日不可留;乱我心者,今日之日多烦忧。长风万里送秋雁,对此可以酣高楼。蓬莱文章建安骨,中间小谢又清发。" +
+               "俱怀逸兴壮思飞,欲上青天揽明月。抽刀断水水更流,举杯销愁愁更愁。人生在世不称意,明朝散发弄扁舟。"
+
+       oldType := s.bucket.Client.Config.AuthVersion
+       oldHeaders := s.bucket.Client.Config.AdditionalHeaders
+
+       s.bucket.Client.Config.AuthVersion = authVersion
+       s.bucket.Client.Config.AdditionalHeaders = extraHeaders
+
+       // Sign URL for function PutObjectWithURL
+       str, err := s.bucket.SignURL(objectName, HTTPPut, 60)
+       c.Assert(err, IsNil)
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Put object with URL
+       err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Sign URL for function GetObjectWithURL
+       str, err = s.bucket.SignURL(objectName, HTTPGet, 60)
+       c.Assert(err, IsNil)
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Get object with URL
+       body, err := s.bucket.GetObjectWithURL(str)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Key with escaped chars
+       objectName = "<>[]()`?.,!@#$%^&'/*-_=+~:;"
+
+       // Sign URL for funciton PutObjectWithURL
+       str, err = s.bucket.SignURL(objectName, HTTPPut, 60)
+       c.Assert(err, IsNil)
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Put object with URL
+       err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Sign URL for function GetObjectWithURL
+       str, err = s.bucket.SignURL(objectName, HTTPGet, 60)
+       c.Assert(err, IsNil)
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Get object with URL
+       body, err = s.bucket.GetObjectWithURL(str)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Key with Chinese chars
+       objectName = "风吹柳花满店香,吴姬压酒劝客尝。金陵子弟来相送,欲行不行各尽觞。请君试问东流水,别意与之谁短长。"
+
+       // Sign URL for function PutObjectWithURL
+       str, err = s.bucket.SignURL(objectName, HTTPPut, 60)
+       c.Assert(err, IsNil)
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Put object with URL
+       err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Sign URL for get function GetObjectWithURL
+       str, err = s.bucket.SignURL(objectName, HTTPGet, 60)
+       c.Assert(err, IsNil)
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Get object with URL
+       body, err = s.bucket.GetObjectWithURL(str)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Key
+       objectName = "test/此情无计可消除/才下眉头/却上 心头/。,;:‘’“”?()『』【】《》!@#¥%……&×/test+ =-_*&^%$#@!`~[]{}()<>|\\/?.,;.txt"
+
+       // Sign URL for function PutObjectWithURL
+       str, err = s.bucket.SignURL(objectName, HTTPPut, 60)
+       c.Assert(err, IsNil)
+
+       // Put object with URL
+       err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Sign URL for function GetObjectWithURL
+       str, err = s.bucket.SignURL(objectName, HTTPGet, 60)
+       c.Assert(err, IsNil)
+
+       // Get object with URL
+       body, err = s.bucket.GetObjectWithURL(str)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Put object
+       err = s.bucket.PutObject(objectName, bytes.NewReader([]byte(objectValue)))
+       c.Assert(err, IsNil)
+
+       // Get object
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Delete object
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       s.bucket.Client.Config.AuthVersion = oldType
+       s.bucket.Client.Config.AdditionalHeaders = oldHeaders
+}
+
+func (s *OssBucketSuite) TestSignURLWithEscapedKey(c *C) {
+       s.SignURLWithEscapedKeyTestFunc(c, AuthV1, []string{})
+       s.SignURLWithEscapedKeyTestFunc(c, AuthV2, []string{})
+       s.SignURLWithEscapedKeyTestFunc(c, AuthV2, []string{"host", "range", "user-agent"})
+}
+
+func (s *OssBucketSuite) SignURLWithEscapedKeyAndPorxyTestFunc(c *C, authVersion AuthVersionType, extraHeaders []string) {
+       // Key with '/'
+       objectName := "zyimg/86/e8/653b5dc97bb0022051a84c632bc4"
+       objectValue := "弃我去者,昨日之日不可留;乱我心者,今日之日多烦忧。长风万里送秋雁,对此可以酣高楼。蓬莱文章建安骨,中间小谢又清发。" +
+               "俱怀逸兴壮思飞,欲上青天揽明月。抽刀断水水更流,举杯销愁愁更愁。人生在世不称意,明朝散发弄扁舟。"
+
+       options := []ClientOption{
+               AuthProxy(proxyHost, proxyUser, proxyPasswd),
+               AuthVersion(authVersion),
+               AdditionalHeaders(extraHeaders),
+       }
+
+       client, err := New(endpoint, accessID, accessKey, options...)
+       bucket, err := client.Bucket(bucketName)
+
+       // Sign URL for put
+       str, err := bucket.SignURL(objectName, HTTPPut, 60)
+       c.Assert(err, IsNil)
+       if bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Put object with URL
+       err = bucket.PutObjectWithURL(str, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Sign URL for function GetObjectWithURL
+       str, err = bucket.SignURL(objectName, HTTPGet, 60)
+       c.Assert(err, IsNil)
+       if bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Get object with URL
+       body, err := bucket.GetObjectWithURL(str)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Key with Chinese chars
+       objectName = "test/此情无计可消除/才下眉头/却上 心头/。,;:‘’“”?()『』【】《》!@#¥%……&×/test+ =-_*&^%$#@!`~[]{}()<>|\\/?.,;.txt"
+
+       // Sign URL for function PutObjectWithURL
+       str, err = bucket.SignURL(objectName, HTTPPut, 60)
+       c.Assert(err, IsNil)
+
+       // Put object with URL
+       err = bucket.PutObjectWithURL(str, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Sign URL for function GetObjectWithURL
+       str, err = bucket.SignURL(objectName, HTTPGet, 60)
+       c.Assert(err, IsNil)
+
+       // Get object with URL
+       body, err = bucket.GetObjectWithURL(str)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Put object
+       err = bucket.PutObject(objectName, bytes.NewReader([]byte(objectValue)))
+       c.Assert(err, IsNil)
+
+       // Get object
+       body, err = bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Delete object
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssBucketSuite) TestSignURLWithEscapedKeyAndPorxy(c *C) {
+       s.SignURLWithEscapedKeyAndPorxyTestFunc(c, AuthV1, []string{})
+       s.SignURLWithEscapedKeyAndPorxyTestFunc(c, AuthV2, []string{})
+       s.SignURLWithEscapedKeyAndPorxyTestFunc(c, AuthV2, []string{"host", "range", "user-agent"})
+}
+
+func (s *OssBucketSuite) TestQueryStringAuthV2(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // set oss v2 signatrue
+       client.Config.AuthVersion = AuthV2
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // build QueryString
+       QueryKey1 := "abc"
+       QueryKey2 := "|abc"
+       c.Assert(strings.Compare(QueryKey1, QueryKey2) < 0, Equals, true)
+       c.Assert(strings.Compare(url.QueryEscape(QueryKey1), url.QueryEscape(QueryKey2)) > 0, Equals, true)
+
+       options := []Option{}
+       params := map[string]interface{}{}
+       params[QueryKey1] = "queryValue1"
+       params[QueryKey2] = "queryValue2"
+       objectKey := objectNamePrefix + RandStr(8)
+       resp, _ := bucket.do("HEAD", objectKey, params, options, nil, nil)
+
+       // object not exist,no signature error
+       c.Assert(resp.StatusCode, Equals, 404)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// TestPutObjectType
+func (s *OssBucketSuite) TestPutObjectType(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "乱石穿空,惊涛拍岸,卷起千堆雪。 江山如画,一时多少豪杰。"
+
+       // Put
+       err := s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("Content-Type"), Equals, "application/octet-stream")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Put
+       err = s.bucket.PutObject(objectName+".txt", strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       meta, err = s.bucket.GetObjectDetailedMeta(objectName + ".txt")
+       c.Assert(err, IsNil)
+       c.Assert(strings.Contains(meta.Get("Content-Type"), "text/plain"), Equals, true)
+
+       err = s.bucket.DeleteObject(objectName + ".txt")
+       c.Assert(err, IsNil)
+
+       // Put
+       err = s.bucket.PutObject(objectName+".apk", strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       meta, err = s.bucket.GetObjectDetailedMeta(objectName + ".apk")
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("Content-Type"), Equals, "application/vnd.android.package-archive")
+
+       err = s.bucket.DeleteObject(objectName + ".txt")
+       c.Assert(err, IsNil)
+}
+
+// TestPutObject
+func (s *OssBucketSuite) TestPutObjectKeyChars(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "白日依山尽,黄河入海流。欲穷千里目,更上一层楼。"
+
+       // Put
+       objectKey := objectName + "十步杀一人,千里不留行。事了拂衣去,深藏身与名"
+       err := s.bucket.PutObject(objectKey, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := s.bucket.GetObject(objectKey)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectKey)
+       c.Assert(err, IsNil)
+
+       // Put
+       objectKey = objectName + "ごきげん如何ですかおれの顔をよく拝んでおけ"
+       err = s.bucket.PutObject(objectKey, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = s.bucket.GetObject(objectKey)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectKey)
+       c.Assert(err, IsNil)
+
+       // Put
+       objectKey = objectName + "~!@#$%^&*()_-+=|\\[]{}<>,./?"
+       err = s.bucket.PutObject(objectKey, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = s.bucket.GetObject(objectKey)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectKey)
+       c.Assert(err, IsNil)
+
+       // Put
+       objectKey = "go/中国 日本 +-#&=*"
+       err = s.bucket.PutObject(objectKey, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = s.bucket.GetObject(objectKey)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectKey)
+       c.Assert(err, IsNil)
+}
+
+// TestPutObjectNegative
+func (s *OssBucketSuite) TestPutObjectNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "大江东去,浪淘尽,千古风流人物。 "
+
+       // Put
+       objectName = objectNamePrefix + RandStr(8)
+       err := s.bucket.PutObject(objectName, strings.NewReader(objectValue),
+               Meta("meta-my", "myprop"))
+       c.Assert(err, IsNil)
+
+       // Check meta
+       body, err := s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-My"), Not(Equals), "myprop")
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Invalid option
+       err = s.bucket.PutObject(objectName, strings.NewReader(objectValue),
+               IfModifiedSince(pastDate))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.PutObjectFromFile(objectName, "bucket.go", IfModifiedSince(pastDate))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.PutObjectFromFile(objectName, "/tmp/xxx")
+       c.Assert(err, NotNil)
+}
+
+// TestPutObjectFromFile
+func (s *OssBucketSuite) TestPutObjectFromFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       localFile := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := "newpic11.jpg"
+
+       // Put
+       err := s.bucket.PutObjectFromFile(objectName, localFile)
+       c.Assert(err, IsNil)
+
+       // Check
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+       eq, err := compareFiles(localFile, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       acl, err := s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("aclRes:", acl)
+       c.Assert(acl.ACL, Equals, "default")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Put with properties
+       options := []Option{
+               Expires(futureDate),
+               ObjectACL(ACLPublicRead),
+               Meta("myprop", "mypropval"),
+       }
+       os.Remove(newFile)
+       err = s.bucket.PutObjectFromFile(objectName, localFile, options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+       eq, err = compareFiles(localFile, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       acl, err = s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectACL:", acl)
+       c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+}
+
+// TestPutObjectFromFile
+func (s *OssBucketSuite) TestPutObjectFromFileType(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       localFile := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       // Put
+       err := s.bucket.PutObjectFromFile(objectName, localFile)
+       c.Assert(err, IsNil)
+
+       // Check
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+       eq, err := compareFiles(localFile, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("Content-Type"), Equals, "image/jpeg")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+}
+
+// TestGetObject
+func (s *OssBucketSuite) TestGetObjectNormal(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "长忆观潮,满郭人争江上望。来疑沧海尽成空,万面鼓声中。弄潮儿向涛头立,手把红旗旗不湿。别来几向梦中看,梦觉尚心寒。"
+
+       // Put
+       err := s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       data, err := ioutil.ReadAll(body)
+       body.Close()
+       str := string(data)
+       c.Assert(str, Equals, objectValue)
+       testLogger.Println("GetObjec:", str)
+
+       // Range
+       var subObjectValue = string(([]byte(objectValue))[15:36])
+       body, err = s.bucket.GetObject(objectName, Range(15, 35))
+       c.Assert(err, IsNil)
+       data, err = ioutil.ReadAll(body)
+       body.Close()
+       str = string(data)
+       c.Assert(str, Equals, subObjectValue)
+       testLogger.Println("GetObject:", str, ",", subObjectValue)
+
+       // If-Modified-Since
+       _, err = s.bucket.GetObject(objectName, IfModifiedSince(futureDate))
+       c.Assert(err, NotNil)
+
+       // If-Unmodified-Since
+       body, err = s.bucket.GetObject(objectName, IfUnmodifiedSince(futureDate))
+       c.Assert(err, IsNil)
+       data, err = ioutil.ReadAll(body)
+       body.Close()
+       c.Assert(string(data), Equals, objectValue)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+
+       // If-Match
+       body, err = s.bucket.GetObject(objectName, IfMatch(meta.Get("Etag")))
+       c.Assert(err, IsNil)
+       data, err = ioutil.ReadAll(body)
+       body.Close()
+       c.Assert(string(data), Equals, objectValue)
+
+       // If-None-Match
+       _, err = s.bucket.GetObject(objectName, IfNoneMatch(meta.Get("Etag")))
+       c.Assert(err, NotNil)
+
+       // process
+       err = s.bucket.PutObjectFromFile(objectName, "../sample/BingWallpaper-2015-11-07.jpg")
+       c.Assert(err, IsNil)
+       _, err = s.bucket.GetObject(objectName, Process("image/format,png"))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestGetObjectNegative
+func (s *OssBucketSuite) TestGetObjectToWriterNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "长忆观潮,满郭人争江上望。"
+
+       // Object not exist
+       _, err := s.bucket.GetObject("NotExist")
+       c.Assert(err, NotNil)
+
+       // Constraint invalid
+       err = s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Out of range
+       _, err = s.bucket.GetObject(objectName, Range(15, 1000))
+       c.Assert(err, IsNil)
+
+       // Not exist
+       err = s.bucket.GetObjectToFile(objectName, "/root1/123abc9874")
+       c.Assert(err, NotNil)
+
+       // Invalid option
+       _, err = s.bucket.GetObject(objectName, ACL(ACLPublicRead))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(objectName, "newpic15.jpg", ACL(ACLPublicRead))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestGetObjectToFile
+func (s *OssBucketSuite) TestGetObjectToFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "江南好,风景旧曾谙;日出江花红胜火,春来江水绿如蓝。能不忆江南?江南忆,最忆是杭州;山寺月中寻桂子,郡亭枕上看潮头。何日更重游!"
+       newFile := RandStr(8) + ".jpg"
+
+       // Put
+       var val = []byte(objectValue)
+       err := s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Check
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+       eq, err := compareFileData(newFile, val)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+       os.Remove(newFile)
+
+       // Range
+       err = s.bucket.GetObjectToFile(objectName, newFile, Range(15, 35))
+       c.Assert(err, IsNil)
+       eq, err = compareFileData(newFile, val[15:36])
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+       os.Remove(newFile)
+
+       err = s.bucket.GetObjectToFile(objectName, newFile, NormalizedRange("15-35"))
+       c.Assert(err, IsNil)
+       eq, err = compareFileData(newFile, val[15:36])
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+       os.Remove(newFile)
+
+       err = s.bucket.GetObjectToFile(objectName, newFile, NormalizedRange("15-"))
+       c.Assert(err, IsNil)
+       eq, err = compareFileData(newFile, val[15:])
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+       os.Remove(newFile)
+
+       err = s.bucket.GetObjectToFile(objectName, newFile, NormalizedRange("-10"))
+       c.Assert(err, IsNil)
+       eq, err = compareFileData(newFile, val[(len(val)-10):len(val)])
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+       os.Remove(newFile)
+
+       // If-Modified-Since
+       err = s.bucket.GetObjectToFile(objectName, newFile, IfModifiedSince(futureDate))
+       c.Assert(err, NotNil)
+
+       // If-Unmodified-Since
+       err = s.bucket.GetObjectToFile(objectName, newFile, IfUnmodifiedSince(futureDate))
+       c.Assert(err, IsNil)
+       eq, err = compareFileData(newFile, val)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+       os.Remove(newFile)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+
+       // If-Match
+       err = s.bucket.GetObjectToFile(objectName, newFile, IfMatch(meta.Get("Etag")))
+       c.Assert(err, IsNil)
+       eq, err = compareFileData(newFile, val)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // If-None-Match
+       err = s.bucket.GetObjectToFile(objectName, newFile, IfNoneMatch(meta.Get("Etag")))
+       c.Assert(err, NotNil)
+
+       // Accept-Encoding:gzip
+       err = s.bucket.PutObjectFromFile(objectName, "../sample/The Go Programming Language.html")
+       c.Assert(err, IsNil)
+       err = s.bucket.GetObjectToFile(objectName, newFile, AcceptEncoding("gzip"))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestListObjects
+func (s *OssBucketSuite) TestListObjects(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       // List empty bucket
+       lor, err := s.bucket.ListObjects()
+       c.Assert(err, IsNil)
+       left := len(lor.Objects)
+
+       // Put three objects
+       err = s.bucket.PutObject(objectName+"1", strings.NewReader(""))
+       c.Assert(err, IsNil)
+       err = s.bucket.PutObject(objectName+"2", strings.NewReader(""))
+       c.Assert(err, IsNil)
+       err = s.bucket.PutObject(objectName+"3", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       // List
+       lor, err = s.bucket.ListObjects()
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, left+3)
+
+       // List with prefix
+       lor, err = s.bucket.ListObjects(Prefix(objectName + "2"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 1)
+
+       lor, err = s.bucket.ListObjects(Prefix(objectName + "22"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 0)
+
+       // List with max keys
+       lor, err = s.bucket.ListObjects(Prefix(objectName), MaxKeys(2))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 2)
+
+       // List with marker
+       lor, err = s.bucket.ListObjects(Marker(objectName+"1"), MaxKeys(1))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 1)
+
+       err = s.bucket.DeleteObject(objectName + "1")
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObject(objectName + "2")
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObject(objectName + "3")
+       c.Assert(err, IsNil)
+}
+
+// TestListObjects
+func (s *OssBucketSuite) TestListObjectsV2NotBatch(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // List empty bucket
+       lor, err := bucket.ListObjectsV2(StartAfter(""))
+       c.Assert(err, IsNil)
+       left := len(lor.Objects)
+
+       // Put three objects
+       err = bucket.PutObject(objectName+"1", strings.NewReader(""))
+       c.Assert(err, IsNil)
+       err = bucket.PutObject(objectName+"2", strings.NewReader(""))
+       c.Assert(err, IsNil)
+       err = bucket.PutObject(objectName+"3", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       // List
+       lor, err = bucket.ListObjectsV2(FetchOwner(true))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, left+3)
+       c.Assert(len(lor.Objects[0].Owner.ID) > 0, Equals, true)
+       c.Assert(len(lor.Objects[0].Owner.DisplayName) > 0, Equals, true)
+
+       // List with prefix
+       lor, err = bucket.ListObjectsV2(Prefix(objectName + "2"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 1)
+       c.Assert(lor.Objects[0].Key, Equals, objectName+"2")
+
+       lor, err = bucket.ListObjectsV2(Prefix(objectName + "22"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 0)
+
+       // List with max keys
+       lor, err = bucket.ListObjectsV2(Prefix(objectName), MaxKeys(2))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 2)
+
+       // List with marker
+       lor, err = bucket.ListObjectsV2(StartAfter(objectName+"1"), MaxKeys(1))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 1)
+       c.Assert(lor.IsTruncated, Equals, true)
+       c.Assert(len(lor.NextContinuationToken) > 0, Equals, true)
+       c.Assert(lor.Objects[0].Key, Equals, objectName+"2")
+
+       lor, err = bucket.ListObjectsV2(Prefix(objectName), StartAfter(objectName+"1"), MaxKeys(2))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 2)
+       c.Assert(lor.IsTruncated, Equals, false)
+       c.Assert(lor.NextContinuationToken, Equals, "")
+       ForceDeleteBucket(client, bucketName, c)
+       c.Assert(lor.Objects[0].Key, Equals, objectName+"2")
+       c.Assert(lor.Objects[1].Key, Equals, objectName+"3")
+}
+
+// TestListObjects
+func (s *OssBucketSuite) TestListObjectsV2BatchList(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // Put three objects
+       count := 17
+       objectName := "testobject-" + RandLowStr(6)
+       for i := 0; i < count; i++ {
+               err = bucket.PutObject(objectName+strconv.Itoa(i), strings.NewReader(""))
+               c.Assert(err, IsNil)
+       }
+
+       Objects := []ObjectProperties{}
+
+       // List Object
+       continuationToken := ""
+       prefix := ""
+       for {
+               lor, err := bucket.ListObjectsV2(Prefix(prefix), ContinuationToken(continuationToken), MaxKeys(3))
+               c.Assert(err, IsNil)
+               Objects = append(Objects, lor.Objects...)
+               continuationToken = lor.NextContinuationToken
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+       c.Assert(len(Objects), Equals, count)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// TestListObjects
+func (s *OssBucketSuite) TestListObjectsEncodingType(c *C) {
+       prefix := objectNamePrefix + "床前明月光,疑是地上霜。举头望明月,低头思故乡。"
+
+       for i := 0; i < 10; i++ {
+               err := s.bucket.PutObject(prefix+strconv.Itoa(i), strings.NewReader(""))
+               c.Assert(err, IsNil)
+       }
+
+       lor, err := s.bucket.ListObjects(Prefix(objectNamePrefix + "床前明月光,"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 10)
+
+       lor, err = s.bucket.ListObjects(Marker(objectNamePrefix + "床前明月光,疑是地上霜。举头望明月,低头思故乡。"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 10)
+
+       lor, err = s.bucket.ListObjects(Prefix(objectNamePrefix + "床前明月光"))
+       c.Assert(err, IsNil)
+       for i, obj := range lor.Objects {
+               c.Assert(obj.Key, Equals, prefix+strconv.Itoa(i))
+       }
+
+       for i := 0; i < 10; i++ {
+               err = s.bucket.DeleteObject(prefix + strconv.Itoa(i))
+               c.Assert(err, IsNil)
+       }
+
+       // Special characters
+       objectName := objectNamePrefix + "` ~ ! @ # $ % ^ & * () - _ + =[] {} \\ | < > , . ? / 0"
+       err = s.bucket.PutObject(objectName, strings.NewReader("明月几时有,把酒问青天"))
+       c.Assert(err, IsNil)
+
+       lor, err = s.bucket.ListObjects(Prefix(objectName))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 1)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       objectName = objectNamePrefix + "中国  日本  +-#&=*"
+       err = s.bucket.PutObject(objectName, strings.NewReader("明月几时有,把酒问青天"))
+       c.Assert(err, IsNil)
+
+       lor, err = s.bucket.ListObjects(Prefix(objectName))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 1)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestIsBucketExist
+func (s *OssBucketSuite) TestIsObjectExist(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       // Put three objects
+       err := s.bucket.PutObject(objectName+"1", strings.NewReader(""))
+       c.Assert(err, IsNil)
+       err = s.bucket.PutObject(objectName+"11", strings.NewReader(""))
+       c.Assert(err, IsNil)
+       err = s.bucket.PutObject(objectName+"111", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       // Exist
+       exist, err := s.bucket.IsObjectExist(objectName + "11")
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, true)
+
+       exist, err = s.bucket.IsObjectExist(objectName + "1")
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, true)
+
+       exist, err = s.bucket.IsObjectExist(objectName + "111")
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, true)
+
+       // Not exist
+       exist, err = s.bucket.IsObjectExist(objectName + "1111")
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, false)
+
+       exist, err = s.bucket.IsObjectExist(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, false)
+
+       err = s.bucket.DeleteObject(objectName + "1")
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObject(objectName + "11")
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObject(objectName + "111")
+       c.Assert(err, IsNil)
+}
+
+// TestDeleteObject
+func (s *OssBucketSuite) TestDeleteObject(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       err := s.bucket.PutObject(objectName, strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       lor, err := s.bucket.ListObjects(Prefix(objectName))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 1)
+
+       // Delete
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Duplicate delete
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       lor, err = s.bucket.ListObjects(Prefix(objectName))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 0)
+}
+
+// TestDeleteObjects
+func (s *OssBucketSuite) TestDeleteObjectsNormal(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       // Delete objects
+       err := s.bucket.PutObject(objectName, strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       res, err := s.bucket.DeleteObjects([]string{objectName})
+       c.Assert(err, IsNil)
+       c.Assert(len(res.DeletedObjects), Equals, 1)
+
+       lor, err := s.bucket.ListObjects(Prefix(objectName))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 0)
+
+       // Delete objects
+       err = s.bucket.PutObject(objectName+"1", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.PutObject(objectName+"2", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       res, err = s.bucket.DeleteObjects([]string{objectName + "1", objectName + "2"})
+       c.Assert(err, IsNil)
+       c.Assert(len(res.DeletedObjects), Equals, 2)
+
+       lor, err = s.bucket.ListObjects(Prefix(objectName))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 0)
+
+       // Delete 0
+       _, err = s.bucket.DeleteObjects([]string{})
+       c.Assert(err, NotNil)
+
+       // DeleteObjectsQuiet
+       err = s.bucket.PutObject(objectName+"1", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.PutObject(objectName+"2", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       res, err = s.bucket.DeleteObjects([]string{objectName + "1", objectName + "2"},
+               DeleteObjectsQuiet(false))
+       c.Assert(err, IsNil)
+       c.Assert(len(res.DeletedObjects), Equals, 2)
+
+       lor, err = s.bucket.ListObjects(Prefix(objectName))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 0)
+
+       // DeleteObjectsQuiet
+       err = s.bucket.PutObject(objectName+"1", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.PutObject(objectName+"2", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       res, err = s.bucket.DeleteObjects([]string{objectName + "1", objectName + "2"},
+               DeleteObjectsQuiet(true))
+       c.Assert(err, IsNil)
+       c.Assert(len(res.DeletedObjects), Equals, 0)
+
+       lor, err = s.bucket.ListObjects(Prefix(objectName))
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, 0)
+
+       // EncodingType
+       err = s.bucket.PutObject("中国人", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       res, err = s.bucket.DeleteObjects([]string{"中国人"})
+       c.Assert(err, IsNil)
+       c.Assert(len(res.DeletedObjects), Equals, 1)
+       c.Assert(res.DeletedObjects[0], Equals, "中国人")
+
+       // EncodingType
+       err = s.bucket.PutObject("中国人", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       res, err = s.bucket.DeleteObjects([]string{"中国人"}, DeleteObjectsQuiet(false))
+       c.Assert(err, IsNil)
+       c.Assert(len(res.DeletedObjects), Equals, 1)
+       c.Assert(res.DeletedObjects[0], Equals, "中国人")
+
+       // EncodingType
+       err = s.bucket.PutObject("中国人", strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       res, err = s.bucket.DeleteObjects([]string{"中国人"}, DeleteObjectsQuiet(true))
+       c.Assert(err, IsNil)
+       c.Assert(len(res.DeletedObjects), Equals, 0)
+
+       // Special characters
+       key := "A ' < > \" & ~ ` ! @ # $ % ^ & * ( ) [] {} - _ + = / | \\ ? . , : ; A"
+       err = s.bucket.PutObject(key, strings.NewReader("value"))
+       c.Assert(err, IsNil)
+
+       _, err = s.bucket.DeleteObjects([]string{key})
+       c.Assert(err, IsNil)
+
+       ress, err := s.bucket.ListObjects(Prefix(key))
+       c.Assert(err, IsNil)
+       c.Assert(len(ress.Objects), Equals, 0)
+
+       // Not exist
+       _, err = s.bucket.DeleteObjects([]string{"NotExistObject"})
+       c.Assert(err, IsNil)
+}
+
+// TestSetObjectMeta
+func (s *OssBucketSuite) TestSetObjectMeta(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       err := s.bucket.PutObject(objectName, strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.SetObjectMeta(objectName,
+               Expires(futureDate),
+               Meta("myprop", "mypropval"))
+       c.Assert(err, IsNil)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("Meta:", meta)
+       c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       acl, err := s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, "default")
+
+       // Invalid option
+       err = s.bucket.SetObjectMeta(objectName, AcceptEncoding("url"))
+       c.Assert(err, IsNil)
+
+       // Invalid option value
+       err = s.bucket.SetObjectMeta(objectName, ServerSideEncryption("invalid"))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Not exist
+       err = s.bucket.SetObjectMeta(objectName, Expires(futureDate))
+       c.Assert(err, NotNil)
+}
+
+// TestGetObjectMeta
+func (s *OssBucketSuite) TestGetObjectMeta(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       // Put
+       err := s.bucket.PutObject(objectName, strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       meta, err := s.bucket.GetObjectMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(len(meta) > 0, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       _, err = s.bucket.GetObjectMeta("NotExistObject")
+       c.Assert(err, NotNil)
+}
+
+// TestGetObjectDetailedMeta
+func (s *OssBucketSuite) TestGetObjectDetailedMeta(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       // Put
+       err := s.bucket.PutObject(objectName, strings.NewReader(""),
+               Expires(futureDate), Meta("myprop", "mypropval"))
+       c.Assert(err, IsNil)
+
+       // Check
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+       c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+       c.Assert(meta.Get("Content-Length"), Equals, "0")
+       c.Assert(len(meta.Get("Date")) > 0, Equals, true)
+       c.Assert(len(meta.Get("X-Oss-Request-Id")) > 0, Equals, true)
+       c.Assert(len(meta.Get("Last-Modified")) > 0, Equals, true)
+
+       // IfModifiedSince/IfModifiedSince
+       _, err = s.bucket.GetObjectDetailedMeta(objectName, IfModifiedSince(futureDate))
+       c.Assert(err, NotNil)
+
+       meta, err = s.bucket.GetObjectDetailedMeta(objectName, IfUnmodifiedSince(futureDate))
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       // IfMatch/IfNoneMatch
+       _, err = s.bucket.GetObjectDetailedMeta(objectName, IfNoneMatch(meta.Get("Etag")))
+       c.Assert(err, NotNil)
+
+       meta, err = s.bucket.GetObjectDetailedMeta(objectName, IfMatch(meta.Get("Etag")))
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       _, err = s.bucket.GetObjectDetailedMeta("NotExistObject")
+       c.Assert(err, NotNil)
+}
+
+// TestSetAndGetObjectAcl
+func (s *OssBucketSuite) TestSetAndGetObjectAcl(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       err := s.bucket.PutObject(objectName, strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       // Default
+       acl, err := s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, "default")
+
+       // Set ACL_PUBLIC_RW
+       err = s.bucket.SetObjectACL(objectName, ACLPublicReadWrite)
+       c.Assert(err, IsNil)
+
+       acl, err = s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite))
+
+       // Set ACL_PRIVATE
+       err = s.bucket.SetObjectACL(objectName, ACLPrivate)
+       c.Assert(err, IsNil)
+
+       acl, err = s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(ACLPrivate))
+
+       // Set ACL_PUBLIC_R
+       err = s.bucket.SetObjectACL(objectName, ACLPublicRead)
+       c.Assert(err, IsNil)
+
+       acl, err = s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestSetAndGetObjectAclNegative
+func (s *OssBucketSuite) TestSetAndGetObjectAclNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       // Object not exist
+       err := s.bucket.SetObjectACL(objectName, ACLPublicRead)
+       c.Assert(err, NotNil)
+}
+
+// TestCopyObject
+func (s *OssBucketSuite) TestCopyObject(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "男儿何不带吴钩,收取关山五十州。请君暂上凌烟阁,若个书生万户侯?"
+
+       err := s.bucket.PutObject(objectName, strings.NewReader(objectValue),
+               ACL(ACLPublicRead), Meta("my", "myprop"))
+       c.Assert(err, IsNil)
+
+       // Copy
+       var objectNameDest = objectName + "dest"
+       _, err = s.bucket.CopyObject(objectName, objectNameDest)
+       c.Assert(err, IsNil)
+
+       // Check
+       lor, err := s.bucket.ListObjects(Prefix(objectName))
+       c.Assert(err, IsNil)
+       testLogger.Println("objects:", lor.Objects)
+       c.Assert(len(lor.Objects), Equals, 2)
+
+       body, err := s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectNameDest)
+       c.Assert(err, IsNil)
+
+       // Copy with constraints x-oss-copy-source-if-modified-since
+       _, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfModifiedSince(futureDate))
+       c.Assert(err, NotNil)
+       testLogger.Println("CopyObject:", err)
+
+       // Copy with constraints x-oss-copy-source-if-unmodified-since
+       _, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfUnmodifiedSince(futureDate))
+       c.Assert(err, IsNil)
+
+       // Check
+       lor, err = s.bucket.ListObjects(Prefix(objectName))
+       c.Assert(err, IsNil)
+       testLogger.Println("objects:", lor.Objects)
+       c.Assert(len(lor.Objects), Equals, 2)
+
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectNameDest)
+       c.Assert(err, IsNil)
+
+       // Copy with constraints x-oss-copy-source-if-match
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+
+       _, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfMatch(meta.Get("Etag")))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectNameDest)
+       c.Assert(err, IsNil)
+
+       // Copy with constraints x-oss-copy-source-if-none-match
+       _, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfNoneMatch(meta.Get("Etag")))
+       c.Assert(err, NotNil)
+
+       // Copy with constraints x-oss-metadata-directive
+       _, err = s.bucket.CopyObject(objectName, objectNameDest, Meta("my", "mydestprop"),
+               MetadataDirective(MetaCopy))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       destMeta, err := s.bucket.GetObjectDetailedMeta(objectNameDest)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+
+       acl, err := s.bucket.GetObjectACL(objectNameDest)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, "default")
+
+       err = s.bucket.DeleteObject(objectNameDest)
+       c.Assert(err, IsNil)
+
+       // Copy with constraints x-oss-metadata-directive and self defined dest object meta
+       options := []Option{
+               ObjectACL(ACLPublicReadWrite),
+               Meta("my", "mydestprop"),
+               MetadataDirective(MetaReplace),
+       }
+       _, err = s.bucket.CopyObject(objectName, objectNameDest, options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       destMeta, err = s.bucket.GetObjectDetailedMeta(objectNameDest)
+       c.Assert(err, IsNil)
+       c.Assert(destMeta.Get("X-Oss-Meta-My"), Equals, "mydestprop")
+
+       acl, err = s.bucket.GetObjectACL(objectNameDest)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite))
+
+       err = s.bucket.DeleteObject(objectNameDest)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestCopyObjectToOrFrom
+func (s *OssBucketSuite) TestCopyObjectToOrFrom(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "男儿何不带吴钩,收取关山五十州。请君暂上凌烟阁,若个书生万户侯?"
+       destBucketName := bucketName + "-dest"
+       objectNameDest := objectName + "-dest"
+
+       err := s.client.CreateBucket(destBucketName)
+       c.Assert(err, IsNil)
+
+       destBucket, err := s.client.Bucket(destBucketName)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Copy from
+       _, err = destBucket.CopyObjectFrom(bucketName, objectName, objectNameDest)
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := destBucket.GetObject(objectNameDest)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Copy to
+       _, err = destBucket.CopyObjectTo(bucketName, objectName, objectNameDest)
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Clean
+       err = destBucket.DeleteObject(objectNameDest)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       err = s.client.DeleteBucket(destBucketName)
+       c.Assert(err, IsNil)
+}
+
+// TestCopyObjectToOrFromNegative
+func (s *OssBucketSuite) TestCopyObjectToOrFromNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       destBucket := bucketName + "-dest"
+       objectNameDest := objectName + "-dest"
+
+       // Object not exist
+       _, err := s.bucket.CopyObjectTo(bucketName, objectName, objectNameDest)
+       c.Assert(err, NotNil)
+
+       // Bucket not exist
+       _, err = s.bucket.CopyObjectFrom(destBucket, objectNameDest, objectName)
+       c.Assert(err, NotNil)
+}
+
+// TestAppendObject
+func (s *OssBucketSuite) TestAppendObject(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,却道海棠依旧。知否?知否?应是绿肥红瘦。"
+       var val = []byte(objectValue)
+       var localFile = RandStr(8) + ".txt"
+       var nextPos int64
+       var midPos = 1 + rand.Intn(len(val)-1)
+
+       var err = CreateFileAndWrite(localFile+"1", val[0:midPos])
+       c.Assert(err, IsNil)
+       err = CreateFileAndWrite(localFile+"2", val[midPos:])
+       c.Assert(err, IsNil)
+
+       // String append
+       nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader("昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,"), nextPos)
+       c.Assert(err, IsNil)
+       nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader("却道海棠依旧。知否?知否?应是绿肥红瘦。"), nextPos)
+       c.Assert(err, IsNil)
+
+       body, err := s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Byte append
+       nextPos = 0
+       nextPos, err = s.bucket.AppendObject(objectName, bytes.NewReader(val[0:midPos]), nextPos)
+       c.Assert(err, IsNil)
+       nextPos, err = s.bucket.AppendObject(objectName, bytes.NewReader(val[midPos:]), nextPos)
+       c.Assert(err, IsNil)
+
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // File append
+       options := []Option{
+               ObjectACL(ACLPublicReadWrite),
+               Meta("my", "myprop"),
+       }
+
+       fd, err := os.Open(localFile + "1")
+       c.Assert(err, IsNil)
+       defer fd.Close()
+       nextPos = 0
+       nextPos, err = s.bucket.AppendObject(objectName, fd, nextPos, options...)
+       c.Assert(err, IsNil)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta, ",", nextPos)
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Appendable")
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("x-oss-Meta-Mine"), Equals, "")
+       c.Assert(meta.Get("X-Oss-Next-Append-Position"), Equals, strconv.FormatInt(nextPos, 10))
+
+       acl, err := s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectACL:", acl)
+       c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite))
+
+       // Second append
+       options = []Option{
+               ObjectACL(ACLPublicRead),
+               Meta("my", "myproptwo"),
+               Meta("mine", "mypropmine"),
+       }
+       fd, err = os.Open(localFile + "2")
+       c.Assert(err, IsNil)
+       defer fd.Close()
+       nextPos, err = s.bucket.AppendObject(objectName, fd, nextPos, options...)
+       c.Assert(err, IsNil)
+
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       meta, err = s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta xxx:", meta)
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Appendable")
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("x-Oss-Meta-Mine"), Equals, "")
+       c.Assert(meta.Get("X-Oss-Next-Append-Position"), Equals, strconv.FormatInt(nextPos, 10))
+
+       acl, err = s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestAppendObjectNegative
+func (s *OssBucketSuite) TestAppendObjectNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       nextPos := int64(0)
+
+       nextPos, err := s.bucket.AppendObject(objectName, strings.NewReader("ObjectValue"), nextPos)
+       c.Assert(err, IsNil)
+
+       nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader("ObjectValue"), 0)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestContentType
+func (s *OssBucketSuite) TestAddContentType(c *C) {
+       opts := AddContentType(nil, "abc.txt")
+       typ, err := FindOption(opts, HTTPHeaderContentType, "")
+       c.Assert(err, IsNil)
+       c.Assert(strings.Contains(typ.(string), "text/plain"), Equals, true)
+
+       opts = AddContentType(nil)
+       typ, err = FindOption(opts, HTTPHeaderContentType, "")
+       c.Assert(err, IsNil)
+       c.Assert(len(opts), Equals, 1)
+       c.Assert(strings.Contains(typ.(string), "application/octet-stream"), Equals, true)
+
+       opts = AddContentType(nil, "abc.txt", "abc.pdf")
+       typ, err = FindOption(opts, HTTPHeaderContentType, "")
+       c.Assert(err, IsNil)
+       c.Assert(strings.Contains(typ.(string), "text/plain"), Equals, true)
+
+       opts = AddContentType(nil, "abc", "abc.txt", "abc.pdf")
+       typ, err = FindOption(opts, HTTPHeaderContentType, "")
+       c.Assert(err, IsNil)
+       c.Assert(strings.Contains(typ.(string), "text/plain"), Equals, true)
+
+       opts = AddContentType(nil, "abc", "abc", "edf")
+       typ, err = FindOption(opts, HTTPHeaderContentType, "")
+       c.Assert(err, IsNil)
+       c.Assert(strings.Contains(typ.(string), "application/octet-stream"), Equals, true)
+
+       opts = AddContentType([]Option{Meta("meta", "my")}, "abc", "abc.txt", "abc.pdf")
+       typ, err = FindOption(opts, HTTPHeaderContentType, "")
+       c.Assert(err, IsNil)
+       c.Assert(len(opts), Equals, 2)
+       c.Assert(strings.Contains(typ.(string), "text/plain"), Equals, true)
+}
+
+func (s *OssBucketSuite) TestGetConfig(c *C) {
+       client, err := New(endpoint, accessID, accessKey, UseCname(true),
+               Timeout(11, 12), SecurityToken("token"), EnableMD5(false))
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       c.Assert(bucket.GetConfig().HTTPTimeout.ConnectTimeout, Equals, time.Second*11)
+       c.Assert(bucket.GetConfig().HTTPTimeout.ReadWriteTimeout, Equals, time.Second*12)
+       c.Assert(bucket.GetConfig().HTTPTimeout.HeaderTimeout, Equals, time.Second*12)
+       c.Assert(bucket.GetConfig().HTTPTimeout.IdleConnTimeout, Equals, time.Second*12)
+       c.Assert(bucket.GetConfig().HTTPTimeout.LongTimeout, Equals, time.Second*12*10)
+
+       c.Assert(bucket.GetConfig().SecurityToken, Equals, "token")
+       c.Assert(bucket.GetConfig().IsCname, Equals, true)
+       c.Assert(bucket.GetConfig().IsEnableMD5, Equals, false)
+}
+
+func (s *OssBucketSuite) TestSTSToken(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "红藕香残玉簟秋。轻解罗裳,独上兰舟。云中谁寄锦书来?雁字回时,月满西楼。"
+
+       stsClient := sts.NewClient(stsaccessID, stsaccessKey, stsARN, "oss_test_sess")
+
+       resp, err := stsClient.AssumeRole(1800)
+       c.Assert(err, IsNil)
+
+       client, err := New(endpoint, resp.Credentials.AccessKeyId, resp.Credentials.AccessKeySecret,
+               SecurityToken(resp.Credentials.SecurityToken))
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // Put
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Get
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // List
+       lor, err := bucket.ListObjects()
+       c.Assert(err, IsNil)
+       testLogger.Println("Objects:", lor.Objects)
+
+       // Put with URL
+       signedURL, err := bucket.SignURL(objectName, HTTPPut, 3600)
+       c.Assert(err, IsNil)
+
+       err = bucket.PutObjectWithURL(signedURL, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Get with URL
+       signedURL, err = bucket.SignURL(objectName, HTTPGet, 3600)
+       c.Assert(err, IsNil)
+
+       body, err = bucket.GetObjectWithURL(signedURL)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Delete
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssBucketSuite) TestSTSTonekNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       localFile := objectName + ".jpg"
+
+       client, err := New(endpoint, accessID, accessKey, SecurityToken("Invalid"))
+       c.Assert(err, IsNil)
+
+       _, err = client.ListBuckets()
+       c.Assert(err, NotNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       err = bucket.PutObject(objectName, strings.NewReader(""))
+       c.Assert(err, NotNil)
+
+       err = bucket.PutObjectFromFile(objectName, "")
+       c.Assert(err, NotNil)
+
+       _, err = bucket.GetObject(objectName)
+       c.Assert(err, NotNil)
+
+       err = bucket.GetObjectToFile(objectName, "")
+       c.Assert(err, NotNil)
+
+       _, err = bucket.ListObjects()
+       c.Assert(err, NotNil)
+
+       err = bucket.SetObjectACL(objectName, ACLPublicRead)
+       c.Assert(err, NotNil)
+
+       _, err = bucket.GetObjectACL(objectName)
+       c.Assert(err, NotNil)
+
+       err = bucket.UploadFile(objectName, localFile, MinPartSize)
+       c.Assert(err, NotNil)
+
+       err = bucket.DownloadFile(objectName, localFile, MinPartSize)
+       c.Assert(err, NotNil)
+
+       _, err = bucket.IsObjectExist(objectName)
+       c.Assert(err, NotNil)
+
+       _, err = bucket.ListMultipartUploads()
+       c.Assert(err, NotNil)
+
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, NotNil)
+
+       _, err = bucket.DeleteObjects([]string{objectName})
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, NotNil)
+}
+
+func (s *OssBucketSuite) TestUploadBigFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       bigFile := "D:\\tmp\\bigfile.zip"
+       newFile := "D:\\tmp\\newbigfile.zip"
+
+       exist, err := isFileExist(bigFile)
+       c.Assert(err, IsNil)
+       if !exist {
+               return
+       }
+
+       // Put
+       start := GetNowSec()
+       err = s.bucket.PutObjectFromFile(objectName, bigFile)
+       c.Assert(err, IsNil)
+       end := GetNowSec()
+       testLogger.Println("Put big file:", bigFile, "use sec:", end-start)
+
+       // Check
+       start = GetNowSec()
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+       end = GetNowSec()
+       testLogger.Println("Get big file:", bigFile, "use sec:", end-start)
+
+       start = GetNowSec()
+       eq, err := compareFiles(bigFile, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+       end = GetNowSec()
+       testLogger.Println("Compare big file:", bigFile, "use sec:", end-start)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssBucketSuite) TestSymlink(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       targetObjectName := objectName + "target"
+
+       err := s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(targetObjectName)
+       c.Assert(err, IsNil)
+
+       meta, err := s.bucket.GetSymlink(objectName)
+       c.Assert(err, NotNil)
+
+       // Put symlink
+       err = s.bucket.PutSymlink(objectName, targetObjectName)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.PutObject(targetObjectName, strings.NewReader("target"))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.PutSymlink(objectName, targetObjectName)
+       c.Assert(err, IsNil)
+
+       meta, err = s.bucket.GetSymlink(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get(HTTPHeaderOssSymlinkTarget), Equals, targetObjectName)
+
+       // List object
+       lor, err := s.bucket.ListObjects()
+       c.Assert(err, IsNil)
+       exist, v := s.getObject(lor.Objects, objectName)
+       c.Assert(exist, Equals, true)
+       c.Assert(v.Type, Equals, "Symlink")
+
+       body, err := s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, "target")
+
+       meta, err = s.bucket.GetSymlink(targetObjectName)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.PutObject(objectName, strings.NewReader("src"))
+       c.Assert(err, IsNil)
+
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, "src")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(targetObjectName)
+       c.Assert(err, IsNil)
+
+       // Put symlink again
+       objectName = objectNamePrefix + RandStr(8)
+       targetObjectName = objectName + "-target"
+
+       err = s.bucket.PutSymlink(objectName, targetObjectName)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.PutObject(targetObjectName, strings.NewReader("target1"))
+       c.Assert(err, IsNil)
+
+       meta, err = s.bucket.GetSymlink(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get(HTTPHeaderOssSymlinkTarget), Equals, targetObjectName)
+
+       body, err = s.bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, "target1")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(targetObjectName)
+       c.Assert(err, IsNil)
+}
+
+// TestRestoreObject
+func (s *OssBucketSuite) TestRestoreObject(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       // List objects
+       lor, err := s.archiveBucket.ListObjects()
+       c.Assert(err, IsNil)
+       left := len(lor.Objects)
+
+       // Put object
+       err = s.archiveBucket.PutObject(objectName, strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       // List
+       lor, err = s.archiveBucket.ListObjects()
+       c.Assert(err, IsNil)
+       c.Assert(len(lor.Objects), Equals, left+1)
+       for _, object := range lor.Objects {
+               c.Assert(object.StorageClass, Equals, string(StorageArchive))
+               c.Assert(object.Type, Equals, "Normal")
+       }
+
+       // Head object
+       meta, err := s.archiveBucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       _, ok := meta["X-Oss-Restore"]
+       c.Assert(ok, Equals, false)
+       c.Assert(meta.Get("X-Oss-Storage-Class"), Equals, "Archive")
+
+       // Error restore object
+       err = s.archiveBucket.RestoreObject("notexistobject")
+       c.Assert(err, NotNil)
+
+       // Restore object
+       err = s.archiveBucket.RestoreObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Head object
+       meta, err = s.archiveBucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Restore"), Equals, "ongoing-request=\"true\"")
+       c.Assert(meta.Get("X-Oss-Storage-Class"), Equals, "Archive")
+}
+
+// TestRestoreObjectWithXml
+func (s *OssBucketSuite) TestRestoreObjectWithConfig(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName, StorageClass(StorageColdArchive))
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       objectName := objectNamePrefix + RandStr(8)
+
+       // Put object
+       err = bucket.PutObject(objectName, strings.NewReader("123456789"), ObjectStorageClass(StorageColdArchive))
+       c.Assert(err, IsNil)
+
+       var restoreConfig RestoreConfiguration
+       restoreConfig.Days = 2
+
+       err = bucket.RestoreObjectDetail(objectName, restoreConfig)
+       c.Assert(err, IsNil)
+
+       objectName = objectNamePrefix + RandStr(8)
+       err = bucket.PutObject(objectName, strings.NewReader("123456789"), ObjectStorageClass(StorageColdArchive))
+       c.Assert(err, IsNil)
+       restoreConfig.Tier = string(RestoreBulk)
+       err = bucket.RestoreObjectDetail(objectName, restoreConfig)
+       c.Assert(err, IsNil)
+
+       objectName = objectNamePrefix + RandStr(8)
+       err = bucket.PutObject(objectName, strings.NewReader("123456789"), ObjectStorageClass(StorageColdArchive))
+       c.Assert(err, IsNil)
+       restoreConfig.Days = 0
+       err = bucket.RestoreObjectDetail(objectName, restoreConfig)
+       c.Assert(err, IsNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// TestRestoreObjectWithXml
+func (s *OssBucketSuite) TestRestoreObjectWithXml(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName, StorageClass(StorageArchive))
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       objectName := objectNamePrefix + RandStr(8)
+
+       // Put object
+       err = bucket.PutObject(objectName, strings.NewReader("123456789"), ObjectStorageClass(StorageArchive))
+       c.Assert(err, IsNil)
+
+       xmlConfig := `<RestoreRequest><Days>7</Days></RestoreRequest>`
+
+       err = bucket.RestoreObjectXML(objectName, xmlConfig)
+       c.Assert(err, IsNil)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// TestProcessObject
+func (s *OssBucketSuite) TestProcessObject(c *C) {
+       objectName := objectNamePrefix + RandStr(8) + ".jpg"
+       err := s.bucket.PutObjectFromFile(objectName, "../sample/BingWallpaper-2015-11-07.jpg")
+       c.Assert(err, IsNil)
+
+       // If bucket-name not specified, it is saved to the current bucket by default.
+       destObjName := objectNamePrefix + RandStr(8) + "-dest.jpg"
+       process := fmt.Sprintf("image/resize,w_100|sys/saveas,o_%v", base64.URLEncoding.EncodeToString([]byte(destObjName)))
+       result, err := s.bucket.ProcessObject(objectName, process)
+       c.Assert(err, IsNil)
+       exist, _ := s.bucket.IsObjectExist(destObjName)
+       c.Assert(exist, Equals, true)
+       c.Assert(result.Bucket, Equals, "")
+       c.Assert(result.Object, Equals, destObjName)
+
+       destObjName = objectNamePrefix + RandStr(8) + "-dest.jpg"
+       process = fmt.Sprintf("image/resize,w_100|sys/saveas,o_%v,b_%v", base64.URLEncoding.EncodeToString([]byte(destObjName)), base64.URLEncoding.EncodeToString([]byte(s.bucket.BucketName)))
+       result, err = s.bucket.ProcessObject(objectName, process)
+       c.Assert(err, IsNil)
+       exist, _ = s.bucket.IsObjectExist(destObjName)
+       c.Assert(exist, Equals, true)
+       c.Assert(result.Bucket, Equals, s.bucket.BucketName)
+       c.Assert(result.Object, Equals, destObjName)
+
+       //no support process
+       process = fmt.Sprintf("image/resize,w_100|saveas,o_%v,b_%v", base64.URLEncoding.EncodeToString([]byte(destObjName)), base64.URLEncoding.EncodeToString([]byte(s.bucket.BucketName)))
+       result, err = s.bucket.ProcessObject(objectName, process)
+       c.Assert(err, NotNil)
+}
+
+// Private
+func CreateFileAndWrite(fileName string, data []byte) error {
+       os.Remove(fileName)
+
+       fo, err := os.Create(fileName)
+       if err != nil {
+               return err
+       }
+       defer fo.Close()
+
+       bytes, err := fo.Write(data)
+       if err != nil {
+               return err
+       }
+
+       if bytes != len(data) {
+               return fmt.Errorf(fmt.Sprintf("write %d bytes not equal data length %d", bytes, len(data)))
+       }
+
+       return nil
+}
+
+// Compare the content between fileL and fileR
+func compareFiles(fileL string, fileR string) (bool, error) {
+       finL, err := os.Open(fileL)
+       if err != nil {
+               return false, err
+       }
+       defer finL.Close()
+
+       finR, err := os.Open(fileR)
+       if err != nil {
+               return false, err
+       }
+       defer finR.Close()
+
+       statL, err := finL.Stat()
+       if err != nil {
+               return false, err
+       }
+
+       statR, err := finR.Stat()
+       if err != nil {
+               return false, err
+       }
+
+       if statL.Size() != statR.Size() {
+               return false, nil
+       }
+
+       size := statL.Size()
+       if size > 102400 {
+               size = 102400
+       }
+
+       bufL := make([]byte, size)
+       bufR := make([]byte, size)
+       for {
+               n, _ := finL.Read(bufL)
+               if 0 == n {
+                       break
+               }
+
+               n, _ = finR.Read(bufR)
+               if 0 == n {
+                       break
+               }
+
+               if !bytes.Equal(bufL, bufR) {
+                       return false, nil
+               }
+       }
+
+       return true, nil
+}
+
+// Compare the content of file and data
+func compareFileData(file string, data []byte) (bool, error) {
+       fin, err := os.Open(file)
+       if err != nil {
+               return false, err
+       }
+       defer fin.Close()
+
+       stat, err := fin.Stat()
+       if err != nil {
+               return false, err
+       }
+
+       if stat.Size() != (int64)(len(data)) {
+               return false, nil
+       }
+
+       buf := make([]byte, stat.Size())
+       n, err := fin.Read(buf)
+       if err != nil {
+               return false, err
+       }
+       if stat.Size() != (int64)(n) {
+               return false, errors.New("read error")
+       }
+
+       if !bytes.Equal(buf, data) {
+               return false, nil
+       }
+
+       return true, nil
+}
+
+func walkDir(dirPth, suffix string) ([]string, error) {
+       var files = []string{}
+       suffix = strings.ToUpper(suffix)
+       err := filepath.Walk(dirPth,
+               func(filename string, fi os.FileInfo, err error) error {
+                       if err != nil {
+                               return err
+                       }
+                       if fi.IsDir() {
+                               return nil
+                       }
+                       if strings.HasSuffix(strings.ToUpper(fi.Name()), suffix) {
+                               files = append(files, filename)
+                       }
+                       return nil
+               })
+       return files, err
+}
+
+func removeTempFiles(path string, prefix string) error {
+       files, err := walkDir(path, prefix)
+       if err != nil {
+               return nil
+       }
+
+       for _, file := range files {
+               os.Remove(file)
+       }
+
+       return nil
+}
+
+func isFileExist(filename string) (bool, error) {
+       _, err := os.Stat(filename)
+       if err != nil && os.IsNotExist(err) {
+               return false, nil
+       } else if err != nil {
+               return false, err
+       } else {
+               return true, nil
+       }
+}
+
+func readBody(body io.ReadCloser) (string, error) {
+       data, err := ioutil.ReadAll(body)
+       body.Close()
+       if err != nil {
+               return "", err
+       }
+       return string(data), nil
+}
+
+func (s *OssBucketSuite) getObject(objects []ObjectProperties, object string) (bool, ObjectProperties) {
+       for _, v := range objects {
+               if v.Key == object {
+                       return true, v
+               }
+       }
+       return false, ObjectProperties{}
+}
+
+func (s *OssBucketSuite) detectUploadSpeed(bucket *Bucket, c *C) (upSpeed int) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       // 1M byte
+       textBuffer := RandStr(1024 * 1024)
+
+       // Put string
+       startT := time.Now()
+       err := bucket.PutObject(objectName, strings.NewReader(textBuffer))
+       endT := time.Now()
+
+       c.Assert(err, IsNil)
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // byte/s
+       upSpeed = len(textBuffer) * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000)
+       return upSpeed
+}
+
+func (s *OssBucketSuite) TestPutSingleObjectLimitSpeed(c *C) {
+
+       // create client and bucket
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.LimitUploadSpeed(1)
+       if err != nil {
+               // go version is less than go1.7,not support limit upload speed
+               // doesn't run this test
+               return
+       }
+       // set unlimited again
+       client.LimitUploadSpeed(0)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       //detect speed:byte/s
+       detectSpeed := s.detectUploadSpeed(bucket, c)
+
+       var limitSpeed = 0
+       if detectSpeed <= perTokenBandwidthSize*2 {
+               limitSpeed = perTokenBandwidthSize
+       } else {
+               //this situation, the test works better
+               limitSpeed = detectSpeed / 2
+       }
+
+       // KB/s
+       err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+
+       // 1M byte
+       textBuffer := RandStr(1024 * 1024)
+
+       // Put body
+       startT := time.Now()
+       err = bucket.PutObject(objectName, strings.NewReader(textBuffer))
+       endT := time.Now()
+
+       realSpeed := int64(len(textBuffer)) * 1000 / (endT.UnixNano()/1000/1000 - startT.UnixNano()/1000/1000)
+
+       fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed)
+
+       c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true)
+
+       if detectSpeed > perTokenBandwidthSize {
+               // the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s)
+               c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true)
+       }
+
+       // Get object and compare content
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, textBuffer)
+
+       bucket.DeleteObject(objectName)
+       client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       return
+}
+
+func putObjectRoutin(bucket *Bucket, object string, textBuffer *string, notifyChan chan int) error {
+       err := bucket.PutObject(object, strings.NewReader(*textBuffer))
+       if err == nil {
+               notifyChan <- 1
+       } else {
+               notifyChan <- 0
+       }
+       return err
+}
+
+func (s *OssBucketSuite) TestPutManyObjectLimitSpeed(c *C) {
+       // create client and bucket
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.LimitUploadSpeed(1)
+       if err != nil {
+               // go version is less than go1.7,not support limit upload speed
+               // doesn't run this test
+               return
+       }
+       // set unlimited
+       client.LimitUploadSpeed(0)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       //detect speed:byte/s
+       detectSpeed := s.detectUploadSpeed(bucket, c)
+       var limitSpeed = 0
+       if detectSpeed <= perTokenBandwidthSize*2 {
+               limitSpeed = perTokenBandwidthSize
+       } else {
+               limitSpeed = detectSpeed / 2
+       }
+
+       // KB/s
+       err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize)
+       c.Assert(err, IsNil)
+
+       // object1
+       objectNameFirst := objectNamePrefix + RandStr(8)
+       objectNameSecond := objectNamePrefix + RandStr(8)
+
+       // 1M byte
+       textBuffer := RandStr(1024 * 1024)
+
+       objectCount := 2
+       notifyChan := make(chan int, objectCount)
+
+       //start routin
+       startT := time.Now()
+       go putObjectRoutin(bucket, objectNameFirst, &textBuffer, notifyChan)
+       go putObjectRoutin(bucket, objectNameSecond, &textBuffer, notifyChan)
+
+       // wait routin end
+       sum := int(0)
+       for j := 0; j < objectCount; j++ {
+               result := <-notifyChan
+               sum += result
+       }
+       endT := time.Now()
+
+       realSpeed := len(textBuffer) * 2 * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000)
+       c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true)
+
+       if detectSpeed > perTokenBandwidthSize {
+               // the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s)
+               c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true)
+       }
+       c.Assert(sum, Equals, 2)
+
+       // Get object and compare content
+       body, err := bucket.GetObject(objectNameFirst)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, textBuffer)
+
+       body, err = bucket.GetObject(objectNameSecond)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, textBuffer)
+
+       // clear bucket and object
+       bucket.DeleteObject(objectNameFirst)
+       bucket.DeleteObject(objectNameSecond)
+       client.DeleteBucket(bucketName)
+
+       fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed)
+
+       return
+}
+
+func (s *OssBucketSuite) TestPutMultipartObjectLimitSpeed(c *C) {
+
+       // create client and bucket
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.LimitUploadSpeed(1)
+       if err != nil {
+               // go version is less than go1.7,not support limit upload speed
+               // doesn't run this test
+               return
+       }
+       // set unlimited
+       client.LimitUploadSpeed(0)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       //detect speed:byte/s
+       detectSpeed := s.detectUploadSpeed(bucket, c)
+
+       var limitSpeed = 0
+       if detectSpeed <= perTokenBandwidthSize*2 {
+               limitSpeed = perTokenBandwidthSize
+       } else {
+               //this situation, the test works better
+               limitSpeed = detectSpeed / 2
+       }
+
+       // KB/s
+       err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "." + string(os.PathSeparator) + objectName
+
+       // 1M byte
+       fileSize := 0
+       textBuffer := RandStr(1024 * 1024)
+       if detectSpeed < perTokenBandwidthSize {
+               ioutil.WriteFile(fileName, []byte(textBuffer), 0644)
+               f, err := os.Stat(fileName)
+               c.Assert(err, IsNil)
+
+               fileSize = int(f.Size())
+               c.Assert(fileSize, Equals, len(textBuffer))
+
+       } else {
+               loopCount := 5
+               f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660)
+               c.Assert(err, IsNil)
+
+               for i := 0; i < loopCount; i++ {
+                       f.Write([]byte(textBuffer))
+               }
+
+               fileInfo, err := f.Stat()
+               c.Assert(err, IsNil)
+
+               fileSize = int(fileInfo.Size())
+               c.Assert(fileSize, Equals, len(textBuffer)*loopCount)
+
+               f.Close()
+       }
+
+       // Put body
+       startT := time.Now()
+       err = bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, ""))
+       endT := time.Now()
+
+       c.Assert(err, IsNil)
+       realSpeed := fileSize * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000)
+       c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true)
+
+       if detectSpeed > perTokenBandwidthSize {
+               // the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s)
+               c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true)
+       }
+
+       // Get object and compare content
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+
+       fileBody, err := ioutil.ReadFile(fileName)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, string(fileBody))
+
+       // delete bucket、object、file
+       bucket.DeleteObject(objectName)
+       client.DeleteBucket(bucketName)
+       os.Remove(fileName)
+
+       fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed)
+
+       return
+}
+
+func (s *OssBucketSuite) TestPutObjectFromFileLimitSpeed(c *C) {
+       // create client and bucket
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.LimitUploadSpeed(1)
+       if err != nil {
+               // go version is less than go1.7,not support limit upload speed
+               // doesn't run this test
+               return
+       }
+       // set unlimited
+       client.LimitUploadSpeed(0)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       //detect speed:byte/s
+       detectSpeed := s.detectUploadSpeed(bucket, c)
+
+       var limitSpeed = 0
+       if detectSpeed <= perTokenBandwidthSize*2 {
+               limitSpeed = perTokenBandwidthSize
+       } else {
+               //this situation, the test works better
+               limitSpeed = detectSpeed / 2
+       }
+
+       // KB/s
+       err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "." + string(os.PathSeparator) + objectName
+
+       // 1M byte
+       fileSize := 0
+       textBuffer := RandStr(1024 * 1024)
+       if detectSpeed < perTokenBandwidthSize {
+               ioutil.WriteFile(fileName, []byte(textBuffer), 0644)
+               f, err := os.Stat(fileName)
+               c.Assert(err, IsNil)
+
+               fileSize = int(f.Size())
+               c.Assert(fileSize, Equals, len(textBuffer))
+
+       } else {
+               loopCount := 2
+               f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660)
+               c.Assert(err, IsNil)
+
+               for i := 0; i < loopCount; i++ {
+                       f.Write([]byte(textBuffer))
+               }
+
+               fileInfo, err := f.Stat()
+               c.Assert(err, IsNil)
+
+               fileSize = int(fileInfo.Size())
+               c.Assert(fileSize, Equals, len(textBuffer)*loopCount)
+
+               f.Close()
+       }
+
+       // Put body
+       startT := time.Now()
+       err = bucket.PutObjectFromFile(objectName, fileName)
+       endT := time.Now()
+
+       c.Assert(err, IsNil)
+       realSpeed := fileSize * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000)
+       c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true)
+
+       if detectSpeed > perTokenBandwidthSize {
+               // the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s)
+               c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true)
+       }
+
+       // Get object and compare content
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+
+       fileBody, err := ioutil.ReadFile(fileName)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, string(fileBody))
+
+       // delete bucket、file、object
+       bucket.DeleteObject(objectName)
+       client.DeleteBucket(bucketName)
+       os.Remove(fileName)
+
+       fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed)
+
+       return
+}
+
+// upload speed limit parameters will not affect download speed
+func (s *OssBucketSuite) TestUploadObjectLimitSpeed(c *C) {
+       // create limit client and bucket
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       tokenCount := 1
+       err = client.LimitUploadSpeed(tokenCount)
+       if err != nil {
+               // go version is less than go1.7,not support limit upload speed
+               // doesn't run this test
+               return
+       }
+       // set unlimited
+       client.LimitUploadSpeed(0)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       //first:upload a object
+       textBuffer := RandStr(1024 * 100)
+       objectName := objectNamePrefix + RandStr(8)
+       err = bucket.PutObject(objectName, strings.NewReader(textBuffer))
+       c.Assert(err, IsNil)
+
+       // limit upload speed
+       err = client.LimitUploadSpeed(tokenCount)
+       c.Assert(err, IsNil)
+
+       // then download the object
+       startT := time.Now()
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       endT := time.Now()
+
+       c.Assert(str, Equals, textBuffer)
+
+       // byte/s
+       downloadSpeed := len(textBuffer) * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000)
+
+       // upload speed limit parameters will not affect download speed
+       c.Assert(downloadSpeed > 2*tokenCount*perTokenBandwidthSize, Equals, true)
+
+       bucket.DeleteObject(objectName)
+       client.DeleteBucket(bucketName)
+}
+
+// test LimitUploadSpeed failure
+func (s *OssBucketSuite) TestLimitUploadSpeedFail(c *C) {
+       // create limit client and bucket
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.LimitUploadSpeed(-1)
+       c.Assert(err, NotNil)
+
+       client.Config = nil
+       err = client.LimitUploadSpeed(100)
+       c.Assert(err, NotNil)
+}
+
+// upload webp object
+func (s *OssBucketSuite) TestUploadObjectWithWebpFormat(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // create webp file
+       textBuffer := RandStr(1024)
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "." + string(os.PathSeparator) + objectName + ".webp"
+       ioutil.WriteFile(fileName, []byte(textBuffer), 0644)
+       _, err = os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       err = bucket.PutObjectFromFile(objectName, fileName)
+       c.Assert(err, IsNil)
+
+       // check object content-type
+       props, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(props["Content-Type"][0], Equals, "image/webp")
+
+       os.Remove(fileName)
+       bucket.DeleteObject(objectName)
+       client.DeleteBucket(bucketName)
+}
+
+func (s *OssBucketSuite) TestPutObjectTagging(c *C) {
+       // put object with tagging
+       objectName := objectNamePrefix + RandStr(8)
+       tag1 := Tag{
+               Key:   RandStr(8),
+               Value: RandStr(9),
+       }
+       tag2 := Tag{
+               Key:   RandStr(10),
+               Value: RandStr(11),
+       }
+       tagging := Tagging{
+               Tags: []Tag{tag1, tag2},
+       }
+       err := s.bucket.PutObject(objectName, strings.NewReader(RandStr(1024)), SetTagging(tagging))
+       c.Assert(err, IsNil)
+
+       headers, err := s.bucket.GetObjectDetailedMeta(objectName)
+       taggingCount, err := strconv.Atoi(headers["X-Oss-Tagging-Count"][0])
+       c.Assert(err, IsNil)
+       c.Assert(taggingCount, Equals, 2)
+
+       // copy object with default option
+       destObjectName := objectNamePrefix + RandStr(8)
+       _, err = s.bucket.CopyObject(objectName, destObjectName)
+       c.Assert(err, IsNil)
+       headers, err = s.bucket.GetObjectDetailedMeta(destObjectName)
+       taggingCount, err = strconv.Atoi(headers["X-Oss-Tagging-Count"][0])
+       c.Assert(err, IsNil)
+       c.Assert(taggingCount, Equals, 2)
+
+       // delete object tagging
+       err = s.bucket.DeleteObjectTagging(objectName)
+       c.Assert(err, IsNil)
+
+       // get object tagging again
+       taggingResult, err := s.bucket.GetObjectTagging(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(len(taggingResult.Tags), Equals, 0)
+
+       // put tagging
+       tag := Tag{
+               Key:   RandStr(8),
+               Value: RandStr(16),
+       }
+       tagging.Tags = []Tag{tag}
+       err = s.bucket.PutObjectTagging(objectName, tagging)
+       c.Assert(err, IsNil)
+
+       taggingResult, err = s.bucket.GetObjectTagging(objectName)
+       c.Assert(len(taggingResult.Tags), Equals, 1)
+       c.Assert(taggingResult.Tags[0].Key, Equals, tag.Key)
+       c.Assert(taggingResult.Tags[0].Value, Equals, tag.Value)
+
+       //put tagging, the length of the key exceeds 128
+       tag = Tag{
+               Key:   RandStr(129),
+               Value: RandStr(16),
+       }
+       tagging.Tags = []Tag{tag}
+       err = s.bucket.PutObjectTagging(objectName, tagging)
+       c.Assert(err, NotNil)
+
+       //put tagging, the length of the value exceeds 256
+       tag = Tag{
+               Key:   RandStr(8),
+               Value: RandStr(257),
+       }
+       tagging.Tags = []Tag{tag}
+       err = s.bucket.PutObjectTagging(objectName, tagging)
+       c.Assert(err, NotNil)
+
+       //put tagging, the lens of tags exceed 10
+       tagging.Tags = []Tag{}
+       for i := 0; i < 11; i++ {
+               tag = Tag{
+                       Key:   RandStr(8),
+                       Value: RandStr(16),
+               }
+               tagging.Tags = append(tagging.Tags, tag)
+       }
+       err = s.bucket.PutObjectTagging(objectName, tagging)
+       c.Assert(err, NotNil)
+
+       //put tagging, invalid value of tag key
+       tag = Tag{
+               Key:   RandStr(8) + "&",
+               Value: RandStr(16),
+       }
+       tagging.Tags = []Tag{tag}
+       err = s.bucket.PutObjectTagging(objectName, tagging)
+       c.Assert(err, NotNil)
+
+       //put tagging, invalid value of tag value
+       tag = Tag{
+               Key:   RandStr(8),
+               Value: RandStr(16) + "&",
+       }
+       tagging.Tags = []Tag{tag}
+       err = s.bucket.PutObjectTagging(objectName, tagging)
+       c.Assert(err, NotNil)
+
+       //put tagging, repeated tag keys
+       tag1 = Tag{
+               Key:   RandStr(8),
+               Value: RandStr(16),
+       }
+       tag2 = Tag{
+               Key:   tag1.Key,
+               Value: RandStr(16),
+       }
+       tagging.Tags = []Tag{tag1, tag2}
+       err = s.bucket.PutObjectTagging(objectName, tagging)
+       c.Assert(err, NotNil)
+
+       s.bucket.DeleteObject(destObjectName)
+       s.bucket.DeleteObject(objectName)
+}
+
+func (s *OssBucketSuite) TestGetObjectTagging(c *C) {
+       // get object which has 2 tags
+       objectName := objectNamePrefix + RandStr(8)
+       tag1 := Tag{
+               Key:   RandStr(8),
+               Value: RandStr(9),
+       }
+       tag2 := Tag{
+               Key:   RandStr(10),
+               Value: RandStr(11),
+       }
+
+       taggingInfo := Tagging{
+               Tags: []Tag{tag1, tag2},
+       }
+
+       err := s.bucket.PutObject(objectName, strings.NewReader(RandStr(1024)), SetTagging(taggingInfo))
+       c.Assert(err, IsNil)
+
+       tagging, err := s.bucket.GetObjectTagging(objectName)
+       c.Assert(len(tagging.Tags), Equals, 2)
+       if tagging.Tags[0].Key == tag1.Key {
+               c.Assert(tagging.Tags[0].Value, Equals, tag1.Value)
+               c.Assert(tagging.Tags[1].Key, Equals, tag2.Key)
+               c.Assert(tagging.Tags[1].Value, Equals, tag2.Value)
+       } else {
+               c.Assert(tagging.Tags[0].Key, Equals, tag2.Key)
+               c.Assert(tagging.Tags[0].Value, Equals, tag2.Value)
+               c.Assert(tagging.Tags[1].Key, Equals, tag1.Key)
+               c.Assert(tagging.Tags[1].Value, Equals, tag1.Value)
+       }
+
+       // get tagging of an object that is not exist
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+       tagging, err = s.bucket.GetObjectTagging(objectName)
+       c.Assert(err, NotNil)
+       c.Assert(len(tagging.Tags), Equals, 0)
+
+       // get object which has no tag
+       objectName = objectNamePrefix + RandStr(8)
+       err = s.bucket.PutObject(objectName, strings.NewReader(RandStr(1024)))
+       c.Assert(err, IsNil)
+       tagging, err = s.bucket.GetObjectTagging(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(len(tagging.Tags), Equals, 0)
+
+       // copy object, with tagging option
+       destObjectName := objectName + "-dest"
+       tagging.Tags = []Tag{tag1, tag2}
+       _, err = s.bucket.CopyObject(objectName, destObjectName, SetTagging(taggingInfo))
+       c.Assert(err, IsNil)
+       tagging, err = s.bucket.GetObjectTagging(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(len(tagging.Tags), Equals, 0)
+
+       // copy object, with tagging option, the value of tagging directive is "REPLACE"
+       tagging.Tags = []Tag{tag1, tag2}
+       _, err = s.bucket.CopyObject(objectName, destObjectName, SetTagging(taggingInfo), TaggingDirective(TaggingReplace))
+       c.Assert(err, IsNil)
+       tagging, err = s.bucket.GetObjectTagging(destObjectName)
+       c.Assert(err, IsNil)
+       c.Assert(len(tagging.Tags), Equals, 2)
+       if tagging.Tags[0].Key == tag1.Key {
+               c.Assert(tagging.Tags[0].Value, Equals, tag1.Value)
+               c.Assert(tagging.Tags[1].Key, Equals, tag2.Key)
+               c.Assert(tagging.Tags[1].Value, Equals, tag2.Value)
+       } else {
+               c.Assert(tagging.Tags[0].Key, Equals, tag2.Key)
+               c.Assert(tagging.Tags[0].Value, Equals, tag2.Value)
+               c.Assert(tagging.Tags[1].Key, Equals, tag1.Key)
+               c.Assert(tagging.Tags[1].Value, Equals, tag1.Value)
+       }
+
+       s.bucket.DeleteObject(objectName)
+       s.bucket.DeleteObject(destObjectName)
+}
+
+func (s *OssBucketSuite) TestDeleteObjectTagging(c *C) {
+       // delete object tagging, the object is not exist
+       objectName := objectNamePrefix + RandStr(8)
+       err := s.bucket.DeleteObjectTagging(objectName)
+       c.Assert(err, NotNil)
+
+       // delete object tagging
+       tag := Tag{
+               Key:   RandStr(8),
+               Value: RandStr(16),
+       }
+       tagging := Tagging{
+               Tags: []Tag{tag},
+       }
+       err = s.bucket.PutObject(objectName, strings.NewReader(RandStr(1024)), SetTagging(tagging))
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObjectTagging(objectName)
+       c.Assert(err, IsNil)
+       taggingResult, err := s.bucket.GetObjectTagging(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(len(taggingResult.Tags), Equals, 0)
+
+       //delete object tagging again
+       err = s.bucket.DeleteObjectTagging(objectName)
+       c.Assert(err, IsNil)
+
+       s.bucket.DeleteObject(objectName)
+}
+
+func (s *OssBucketSuite) TestUploadFileMimeShtml(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "oss-sdk-test-file-" + RandLowStr(5) + ".shtml"
+       CreateFile(fileName, "123", c)
+
+       err = bucket.PutObjectFromFile(objectName, fileName)
+       c.Assert(err, IsNil)
+
+       headResult, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       strContentType := headResult.Get("Content-Type")
+       c.Assert(strings.Contains(strContentType, "text/html"), Equals, true)
+       os.Remove(fileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningBucketVerison(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // Get default bucket info
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "")
+       c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "")
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, "")
+
+       // put bucket version:enabled
+       var respHeader http.Header
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       c.Assert(GetRequestId(respHeader) != "", Equals, true)
+
+       bucketResult, err = client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+       // put bucket version:Suspended
+       versioningConfig.Status = string(VersionSuspended)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       bucketResult, err = client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionSuspended))
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningPutAndGetObject(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       // get object v1
+       body, err := bucket.GetObject(objectName, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       body.Close()
+       c.Assert(str, Equals, contextV1)
+
+       // get object v2
+       body, err = bucket.GetObject(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       body.Close()
+       c.Assert(str, Equals, contextV2)
+
+       // get object without version
+       body, err = bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       body.Close()
+       c.Assert(str, Equals, contextV2)
+
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV1))
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningHeadObject(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       // head object v1
+       headResultV1, err := bucket.GetObjectMeta(objectName, VersionId(versionIdV1))
+       objLen, err := strconv.Atoi(headResultV1.Get("Content-Length"))
+       c.Assert(objLen, Equals, len(contextV1))
+
+       headResultV1, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV1))
+       objLen, err = strconv.Atoi(headResultV1.Get("Content-Length"))
+       c.Assert(objLen, Equals, len(contextV1))
+
+       // head object v2
+       headResultV2, err := bucket.GetObjectMeta(objectName, VersionId(versionIdV2))
+       objLen, err = strconv.Atoi(headResultV2.Get("Content-Length"))
+       c.Assert(objLen, Equals, len(contextV2))
+
+       headResultV2, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV2))
+       objLen, err = strconv.Atoi(headResultV2.Get("Content-Length"))
+       c.Assert(objLen, Equals, len(contextV2))
+
+       // head object without version
+       // head object v2
+       headResult, err := bucket.GetObjectMeta(objectName)
+       objLen, err = strconv.Atoi(headResult.Get("Content-Length"))
+       c.Assert(objLen, Equals, len(contextV2))
+
+       headResult, err = bucket.GetObjectDetailedMeta(objectName)
+       objLen, err = strconv.Atoi(headResultV2.Get("Content-Length"))
+       c.Assert(objLen, Equals, len(contextV2))
+
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV1))
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningDeleteLatestVersionObject(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       // delete v2 object:permently delete
+       options := []Option{VersionId(versionIdV2), GetResponseHeader(&respHeader)}
+       err = bucket.DeleteObject(objectName, options...)
+       c.Assert(err, IsNil)
+       c.Assert(GetVersionId(respHeader), Equals, versionIdV2)
+
+       // get v2 object failure
+       body, err := bucket.GetObject(objectName, VersionId(versionIdV2))
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "NoSuchVersion")
+
+       // get v1 object success
+       body, err = bucket.GetObject(objectName, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       body.Close()
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, contextV1)
+
+       // get default object success:v1
+       body, err = bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       body.Close()
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, contextV1)
+
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV1))
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningDeleteOldVersionObject(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       // delete v1 object:permently delete
+       options := []Option{VersionId(versionIdV1), GetResponseHeader(&respHeader)}
+       err = bucket.DeleteObject(objectName, options...)
+       c.Assert(err, IsNil)
+       c.Assert(GetVersionId(respHeader), Equals, versionIdV1)
+
+       // get v2 object success
+       body, err := bucket.GetObject(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       body.Close()
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, contextV2)
+
+       // get v1 object faliure
+       body, err = bucket.GetObject(objectName, VersionId(versionIdV1))
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "NoSuchVersion")
+
+       // get default object success:v2
+       body, err = bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       body.Close()
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, contextV2)
+
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV1))
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningDeleteDefaultVersionObject(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       // delete default object:mark delete v2
+       options := []Option{GetResponseHeader(&respHeader)}
+       err = bucket.DeleteObject(objectName, options...)
+       c.Assert(err, IsNil)
+
+       markVersionId := GetVersionId(respHeader)
+       c.Assert(len(markVersionId) > 0, Equals, true)
+       c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true")
+
+       // get v2 object success
+       body, err := bucket.GetObject(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       body.Close()
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, contextV2)
+
+       // get v1 object success
+       body, err = bucket.GetObject(objectName, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       body.Close()
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, contextV1)
+
+       // get default object failure:marker v2
+       body, err = bucket.GetObject(objectName, GetResponseHeader(&respHeader))
+       c.Assert(err, NotNil)
+       c.Assert(err.(ServiceError).Code, Equals, "NoSuchKey")
+       c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true")
+
+       // delete mark v2
+       options = []Option{VersionId(markVersionId), GetResponseHeader(&respHeader)}
+       err = bucket.DeleteObject(objectName, options...)
+       c.Assert(err, IsNil)
+       c.Assert(GetVersionId(respHeader), Equals, markVersionId)
+
+       // get default object success:v2
+       body, err = bucket.GetObject(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       body.Close()
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, contextV2)
+
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV1))
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningListObjectVersions(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       // delete default object:mark delete v2
+       options := []Option{GetResponseHeader(&respHeader)}
+       err = bucket.DeleteObject(objectName, options...)
+       c.Assert(err, IsNil)
+
+       markVersionId := GetVersionId(respHeader)
+       c.Assert(len(markVersionId) > 0, Equals, true)
+       c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true")
+
+       // delete default object again:mark delete v2
+       err = bucket.DeleteObject(objectName, options...)
+       c.Assert(err, IsNil)
+       markVersionIdAgain := GetVersionId(respHeader)
+       c.Assert(len(markVersionIdAgain) > 0, Equals, true)
+       c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true")
+       c.Assert(markVersionId != markVersionIdAgain, Equals, true)
+
+       // list bucket versions
+       listResult, err := bucket.ListObjectVersions()
+       c.Assert(err, IsNil)
+       c.Assert(len(listResult.ObjectDeleteMarkers), Equals, 2)
+       c.Assert(len(listResult.ObjectVersions), Equals, 2)
+       mapMarkVersion := map[string]string{}
+       mapMarkVersion[listResult.ObjectDeleteMarkers[0].VersionId] = listResult.ObjectDeleteMarkers[0].VersionId
+       mapMarkVersion[listResult.ObjectDeleteMarkers[1].VersionId] = listResult.ObjectDeleteMarkers[1].VersionId
+
+       // check delete mark
+       _, ok := mapMarkVersion[markVersionId]
+       c.Assert(ok == true, Equals, true)
+       _, ok = mapMarkVersion[markVersionIdAgain]
+       c.Assert(ok == true, Equals, true)
+
+       // check versionId
+       mapVersion := map[string]string{}
+       mapVersion[listResult.ObjectVersions[0].VersionId] = listResult.ObjectVersions[0].VersionId
+       mapVersion[listResult.ObjectVersions[1].VersionId] = listResult.ObjectVersions[1].VersionId
+       _, ok = mapVersion[versionIdV1]
+       c.Assert(ok == true, Equals, true)
+       _, ok = mapVersion[versionIdV2]
+       c.Assert(ok == true, Equals, true)
+
+       // delete deleteMark v2
+       options = []Option{VersionId(markVersionId), GetResponseHeader(&respHeader)}
+       err = bucket.DeleteObject(objectName, options...)
+       c.Assert(err, IsNil)
+       c.Assert(GetVersionId(respHeader), Equals, markVersionId)
+
+       // delete deleteMark v2 again
+       options = []Option{VersionId(markVersionIdAgain), GetResponseHeader(&respHeader)}
+       err = bucket.DeleteObject(objectName, options...)
+       c.Assert(err, IsNil)
+       c.Assert(GetVersionId(respHeader), Equals, markVersionIdAgain)
+
+       // delete versionId
+       bucket.DeleteObject(objectName, VersionId(versionIdV1))
+       bucket.DeleteObject(objectName, VersionId(versionIdV2))
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningBatchDeleteVersionObjects(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+       // put object v1
+       objectName1 := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       objectName2 := objectNamePrefix + RandStr(8)
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       //batch delete objects
+       versionIds := []DeleteObject{DeleteObject{Key: objectName1, VersionId: versionIdV1},
+               DeleteObject{Key: objectName2, VersionId: versionIdV2}}
+       deleteResult, err := bucket.DeleteObjectVersions(versionIds)
+       c.Assert(err, IsNil)
+       c.Assert(len(deleteResult.DeletedObjectsDetail), Equals, 2)
+
+       // check delete detail info:key
+       deleteMap := map[string]string{}
+       deleteMap[deleteResult.DeletedObjectsDetail[0].Key] = deleteResult.DeletedObjectsDetail[0].VersionId
+       deleteMap[deleteResult.DeletedObjectsDetail[1].Key] = deleteResult.DeletedObjectsDetail[1].VersionId
+       id1, ok := deleteMap[objectName1]
+       c.Assert(ok, Equals, true)
+       c.Assert(id1, Equals, versionIdV1)
+
+       id2, ok := deleteMap[objectName2]
+       c.Assert(ok, Equals, true)
+       c.Assert(id2, Equals, versionIdV2)
+
+       // list bucket versions
+       listResult, err := bucket.ListObjectVersions()
+       c.Assert(err, IsNil)
+       c.Assert(len(listResult.ObjectDeleteMarkers), Equals, 0)
+       c.Assert(len(listResult.ObjectVersions), Equals, 0)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningBatchDeleteDefaultVersionObjects(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+       // put object v1
+       objectName1 := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       objectName2 := objectNamePrefix + RandStr(8)
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       //batch delete objects
+       versionIds := []DeleteObject{DeleteObject{Key: objectName1, VersionId: ""},
+               DeleteObject{Key: objectName2, VersionId: ""}}
+       deleteResult, err := bucket.DeleteObjectVersions(versionIds)
+       c.Assert(err, IsNil)
+
+       // check delete detail info:key
+       deleteDetailMap := map[string]DeletedKeyInfo{}
+       deleteDetailMap[deleteResult.DeletedObjectsDetail[0].Key] = deleteResult.DeletedObjectsDetail[0]
+       deleteDetailMap[deleteResult.DeletedObjectsDetail[1].Key] = deleteResult.DeletedObjectsDetail[1]
+       keyInfo1, ok := deleteDetailMap[objectName1]
+       c.Assert(ok, Equals, true)
+       c.Assert(keyInfo1.Key, Equals, objectName1)
+       c.Assert(keyInfo1.VersionId, Equals, "")
+       c.Assert(keyInfo1.DeleteMarker, Equals, true)
+       c.Assert(keyInfo1.DeleteMarkerVersionId != versionIdV1, Equals, true)
+
+       keyInfo2, ok := deleteDetailMap[objectName2]
+       c.Assert(ok, Equals, true)
+       c.Assert(keyInfo2.Key, Equals, objectName2)
+       c.Assert(keyInfo2.VersionId, Equals, "")
+       c.Assert(keyInfo2.DeleteMarker, Equals, true)
+       c.Assert(keyInfo2.DeleteMarkerVersionId != versionIdV2, Equals, true)
+
+       // list bucket versions
+       listResult, err := bucket.ListObjectVersions()
+       c.Assert(err, IsNil)
+       c.Assert(len(listResult.ObjectDeleteMarkers), Equals, 2)
+       c.Assert(len(listResult.ObjectVersions), Equals, 2)
+
+       // delete version object
+       versionIds = []DeleteObject{DeleteObject{Key: objectName1, VersionId: versionIdV1},
+               DeleteObject{Key: objectName2, VersionId: versionIdV2}}
+       deleteResult, err = bucket.DeleteObjectVersions(versionIds)
+       c.Assert(err, IsNil)
+
+       // delete deleteMark object
+       versionIds = []DeleteObject{DeleteObject{Key: objectName1, VersionId: keyInfo1.DeleteMarkerVersionId},
+               DeleteObject{Key: objectName2, VersionId: keyInfo2.DeleteMarkerVersionId}}
+       deleteResult, err = bucket.DeleteObjectVersions(versionIds)
+       c.Assert(err, IsNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// bucket has no versioning flag
+func (s *OssBucketSuite) TestVersioningBatchDeleteNormalObjects(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // not put bucket versioning
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put object v1
+       objectName1 := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1), Equals, 0)
+
+       // put object v2
+       objectName2 := objectNamePrefix + RandStr(8)
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2), Equals, 0)
+
+       //batch delete objects
+       keys := []string{objectName1, objectName2}
+       deleteResult, err := bucket.DeleteObjects(keys)
+       c.Assert(len(deleteResult.DeletedObjects), Equals, 2)
+
+       // check delete info
+       deleteMap := map[string]string{}
+       deleteMap[deleteResult.DeletedObjects[0]] = deleteResult.DeletedObjects[0]
+       deleteMap[deleteResult.DeletedObjects[1]] = deleteResult.DeletedObjects[1]
+       _, ok := deleteMap[objectName1]
+       c.Assert(ok, Equals, true)
+       _, ok = deleteMap[objectName2]
+       c.Assert(ok, Equals, true)
+
+       ForceDeleteBucket(client, bucketName, c)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssBucketSuite) TestVersioningSymlink(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // put object 1
+       objectName1 := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object 2
+       objectName2 := objectNamePrefix + RandStr(8)
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       // put symlink for object 1
+       linkName := objectNamePrefix + RandStr(8)
+       err = bucket.PutSymlink(linkName, objectName1, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       linkVersionIdV1 := GetVersionId(respHeader)
+
+       // PutSymlink for object 2
+       err = bucket.PutSymlink(linkName, objectName2, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       linkVersionIdV2 := GetVersionId(respHeader)
+
+       // check v1 and v2
+       c.Assert(linkVersionIdV1 != linkVersionIdV2, Equals, true)
+
+       // GetSymlink for object1
+       getResult, err := bucket.GetSymlink(linkName, VersionId(linkVersionIdV1))
+       c.Assert(err, IsNil)
+       c.Assert(getResult.Get("x-oss-symlink-target"), Equals, objectName1)
+
+       // GetSymlink for object2
+       getResult, err = bucket.GetSymlink(linkName, VersionId(linkVersionIdV2))
+       c.Assert(err, IsNil)
+       c.Assert(getResult.Get("x-oss-symlink-target"), Equals, objectName2)
+
+       bucket.DeleteObject(linkName)
+       bucket.DeleteObject(objectName1)
+       bucket.DeleteObject(objectName2)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningObjectAcl(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       // put Acl for v1
+       err = bucket.SetObjectACL(objectName, ACLPublicRead, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+
+       // put Acl for v2
+       err = bucket.SetObjectACL(objectName, ACLPublicReadWrite, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+
+       // GetAcl for v1
+       getResult, err := bucket.GetObjectACL(objectName, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       c.Assert(getResult.ACL, Equals, string(ACLPublicRead))
+
+       // GetAcl for v2
+       getResult, err = bucket.GetObjectACL(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       c.Assert(getResult.ACL, Equals, string(ACLPublicReadWrite))
+
+       // delete default version
+       err = bucket.DeleteObject(objectName, GetResponseHeader(&respHeader))
+       c.Assert(len(GetVersionId(respHeader)) > 0, Equals, true)
+       c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true")
+
+       // GetAcl for v1 agagin
+       getResult, err = bucket.GetObjectACL(objectName, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       c.Assert(getResult.ACL, Equals, string(ACLPublicRead))
+
+       // GetAcl for v2 again
+       getResult, err = bucket.GetObjectACL(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       c.Assert(getResult.ACL, Equals, string(ACLPublicReadWrite))
+
+       // GetAcl for default failure
+       getResult, err = bucket.GetObjectACL(objectName)
+       c.Assert(err, NotNil)
+
+       bucket.DeleteObject(objectName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningAppendObject(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // append object
+       var nextPos int64 = 0
+       var respHeader http.Header
+       objectName := objectNamePrefix + RandStr(8)
+       nextPos, err = bucket.AppendObject(objectName, strings.NewReader("123"), nextPos, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       c.Assert(GetVersionId(respHeader), Equals, NullVersion)
+
+       nextPos, err = bucket.AppendObject(objectName, strings.NewReader("456"), nextPos, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       c.Assert(GetVersionId(respHeader), Equals, NullVersion)
+
+       // delete object
+       err = bucket.DeleteObject(objectName, GetResponseHeader(&respHeader))
+       markVersionId := GetVersionId(respHeader)
+
+       // get default object failure
+       _, err = bucket.GetObject(objectName)
+       c.Assert(err, NotNil)
+
+       // get null version success
+       body, err := bucket.GetObject(objectName, VersionId(NullVersion))
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, "123456")
+
+       // append object again:failure
+       nextPos, err = bucket.AppendObject(objectName, strings.NewReader("789"), nextPos, GetResponseHeader(&respHeader))
+       c.Assert(err, NotNil)
+
+       // delete deletemark
+       options := []Option{VersionId(markVersionId), GetResponseHeader(&respHeader)}
+       err = bucket.DeleteObject(objectName, options...)
+       c.Assert(markVersionId, Equals, GetVersionId(respHeader))
+
+       // append object again:success
+       nextPos, err = bucket.AppendObject(objectName, strings.NewReader("789"), nextPos, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       c.Assert(int(nextPos), Equals, 9)
+
+       bucket.DeleteObject(objectName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningCopyObject(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       destObjectKey := objectNamePrefix + RandStr(8)
+
+       // copyobject default
+       _, err = bucket.CopyObject(objectName, destObjectKey, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       srcVersionId := GetCopySrcVersionId(respHeader)
+       c.Assert(srcVersionId, Equals, versionIdV2)
+
+       body, err := bucket.GetObject(destObjectKey)
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, contextV2)
+
+       //  copyobject v1
+       options := []Option{VersionId(versionIdV1), GetResponseHeader(&respHeader)}
+       _, err = bucket.CopyObject(objectName, destObjectKey, options...)
+       c.Assert(err, IsNil)
+       srcVersionId = GetCopySrcVersionId(respHeader)
+       c.Assert(srcVersionId, Equals, versionIdV1)
+
+       body, err = bucket.GetObject(destObjectKey)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, contextV1)
+
+       // delete object
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // default copyobject again,failuer
+       _, err = bucket.CopyObject(objectName, destObjectKey, GetResponseHeader(&respHeader))
+       c.Assert(err, NotNil)
+
+       bucket.DeleteObject(objectName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningCompleteMultipartUpload(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+       var fileName = "test-file-" + RandStr(8)
+       content := RandStr(500 * 1024)
+       CreateFile(fileName, content, c)
+
+       chunks, err := SplitFileByPartNum(fileName, 3)
+       c.Assert(err, IsNil)
+
+       options := []Option{
+               Expires(futureDate), Meta("my", "myprop"),
+       }
+
+       fd, err := os.Open(fileName)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       imur, err := bucket.InitiateMultipartUpload(objectName, options...)
+       c.Assert(err, IsNil)
+       var parts []UploadPart
+       for _, chunk := range chunks {
+               fd.Seek(chunk.Offset, os.SEEK_SET)
+               part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number)
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       var respHeader http.Header
+       _, err = bucket.CompleteMultipartUpload(imur, parts, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       //get versionId
+       versionIdV1 := GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       meta, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+       // put object agagin
+       err = bucket.PutObject(objectName, strings.NewReader(""), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 := GetVersionId(respHeader)
+       c.Assert(versionIdV1 == versionIdV2, Equals, false)
+
+       // get meta v1
+       meta, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(len(content)))
+
+       // get meta v2
+       meta, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(0))
+
+       os.Remove(fileName)
+       bucket.DeleteObject(objectName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningUploadPartCopy(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // upload mutlipart object with v1
+       multiName := objectNamePrefix + RandStr(8)
+       var parts []UploadPart
+       imur, err := bucket.InitiateMultipartUpload(multiName)
+       c.Assert(err, IsNil)
+
+       part, err := bucket.UploadPartCopy(imur, bucketName, objectName, 0, int64(len(contextV1)), 1, VersionId(versionIdV1))
+       parts = []UploadPart{part}
+       c.Assert(err, IsNil)
+
+       _, err = bucket.CompleteMultipartUpload(imur, parts, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       //get versionId
+       partVersionIdV1 := GetVersionId(respHeader)
+       c.Assert(len(partVersionIdV1) > 0, Equals, true)
+
+       // get meta v1
+       meta, err := bucket.GetObjectDetailedMeta(multiName, VersionId(partVersionIdV1))
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(len(contextV1)))
+
+       // upload mutlipart object with v2
+       imur, err = bucket.InitiateMultipartUpload(multiName)
+       part, err = bucket.UploadPartCopy(imur, bucketName, objectName, 0, int64(len(contextV2)), 1, VersionId(versionIdV2))
+       parts = []UploadPart{part}
+
+       _, err = bucket.CompleteMultipartUpload(imur, parts, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       //get versionId
+       partVersionIdV2 := GetVersionId(respHeader)
+       c.Assert(len(partVersionIdV2) > 0, Equals, true)
+
+       // get meta v2
+       meta, err = bucket.GetObjectDetailedMeta(multiName, VersionId(partVersionIdV2))
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(len(contextV2)))
+
+       bucket.DeleteObject(objectName)
+       bucket.DeleteObject(multiName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningRestoreObject(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName, StorageClass(StorageArchive))
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // RestoreObject v1
+       options := []Option{GetResponseHeader(&respHeader), VersionId(versionIdV1)}
+       err = bucket.RestoreObject(objectName, options...)
+       c.Assert(err, IsNil)
+       c.Assert(GetVersionId(respHeader), Equals, versionIdV1)
+
+       // RestoreObject v2
+       options = []Option{GetResponseHeader(&respHeader), VersionId(versionIdV2)}
+       err = bucket.RestoreObject(objectName, options...)
+       c.Assert(err, IsNil)
+       c.Assert(GetVersionId(respHeader), Equals, versionIdV2)
+
+       bucket.DeleteObject(objectName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningObjectTagging(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName, StorageClass(StorageArchive))
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // ObjectTagging v1
+       var tagging1 Tagging
+       tagging1.Tags = []Tag{Tag{Key: "testkey1", Value: "testvalue1"}}
+       err = bucket.PutObjectTagging(objectName, tagging1, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       getResult, err := bucket.GetObjectTagging(objectName, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       c.Assert(getResult.Tags[0].Key, Equals, tagging1.Tags[0].Key)
+       c.Assert(getResult.Tags[0].Value, Equals, tagging1.Tags[0].Value)
+
+       // ObjectTagging v2
+       var tagging2 Tagging
+       tagging2.Tags = []Tag{Tag{Key: "testkey2", Value: "testvalue2"}}
+       err = bucket.PutObjectTagging(objectName, tagging2, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       getResult, err = bucket.GetObjectTagging(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       c.Assert(getResult.Tags[0].Key, Equals, tagging2.Tags[0].Key)
+       c.Assert(getResult.Tags[0].Value, Equals, tagging2.Tags[0].Value)
+
+       // delete ObjectTagging v2
+       err = bucket.DeleteObjectTagging(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+
+       getResult, err = bucket.GetObjectTagging(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       c.Assert(len(getResult.Tags), Equals, 0)
+
+       bucket.DeleteObject(objectName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningIsObjectExist(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       // check default exist
+       exist, err := bucket.IsObjectExist(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, true)
+
+       // check object v1 exist
+       exist, err = bucket.IsObjectExist(objectName, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, true)
+
+       // check object v2 exist
+       exist, err = bucket.IsObjectExist(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, true)
+
+       // rm v2
+       err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+
+       // check object v2 not exist
+       exist, err = bucket.IsObjectExist(objectName, VersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, false)
+
+       // rm default
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // check default exist
+       exist, err = bucket.IsObjectExist(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, false)
+
+       // check v1 exist
+       exist, err = bucket.IsObjectExist(objectName, VersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, true)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestOptionsMethod(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket cors
+       var rule = CORSRule{
+               AllowedOrigin: []string{"www.aliyun.com"},
+               AllowedMethod: []string{"PUT", "GET", "POST"},
+               AllowedHeader: []string{"x-oss-meta-author"},
+               ExposeHeader:  []string{"x-oss-meta-name"},
+               MaxAgeSeconds: 100,
+       }
+
+       // set cors
+       err = client.SetBucketCORS(bucketName, []CORSRule{rule})
+       c.Assert(err, IsNil)
+
+       // bucket options success
+       options := []Option{}
+       originOption := Origin("www.aliyun.com")
+       acMethodOption := ACReqMethod("PUT")
+       acHeadersOption := ACReqHeaders("x-oss-meta-author")
+       options = append(options, originOption)
+       options = append(options, acMethodOption)
+       options = append(options, acHeadersOption)
+       _, err = bucket.OptionsMethod("", options...)
+       c.Assert(err, IsNil)
+
+       // options failure
+       options = []Option{}
+       originOption = Origin("www.aliyun.com")
+       acMethodOption = ACReqMethod("PUT")
+       acHeadersOption = ACReqHeaders("x-oss-meta-author-1")
+       options = append(options, originOption)
+       options = append(options, acMethodOption)
+       options = append(options, acHeadersOption)
+       _, err = bucket.OptionsMethod("", options...)
+       c.Assert(err, NotNil)
+
+       // put object
+       objectName := objectNamePrefix + RandStr(8)
+       context := RandStr(100)
+       err = bucket.PutObject(objectName, strings.NewReader(context))
+       c.Assert(err, IsNil)
+
+       // object options success
+       options = []Option{}
+       originOption = Origin("www.aliyun.com")
+       acMethodOption = ACReqMethod("PUT")
+       acHeadersOption = ACReqHeaders("x-oss-meta-author")
+       options = append(options, originOption)
+       options = append(options, acMethodOption)
+       options = append(options, acHeadersOption)
+       _, err = bucket.OptionsMethod("", options...)
+       c.Assert(err, IsNil)
+
+       // options failure
+       options = []Option{}
+       originOption = Origin("www.aliyun.com")
+       acMethodOption = ACReqMethod("PUT")
+       acHeadersOption = ACReqHeaders("x-oss-meta-author-1")
+       options = append(options, originOption)
+       options = append(options, acMethodOption)
+       options = append(options, acHeadersOption)
+       _, err = bucket.OptionsMethod("", options...)
+       c.Assert(err, NotNil)
+
+       bucket.DeleteObject(objectName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestBucketTrafficLimitObject1(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       var respHeader http.Header
+       var qosDelayTime string
+       var traffic int64 = 819220 // 100KB
+       maxTraffic := traffic * 120 / 100
+
+       objectName := objectNamePrefix + RandStr(8)
+       localFile := "../sample/BingWallpaper-2015-11-07.jpg"
+       fd, err := os.Open(localFile)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       tryGetFileSize := func(f *os.File) int64 {
+               fInfo, _ := f.Stat()
+               return fInfo.Size()
+       }
+       contentLength := tryGetFileSize(fd) * 8
+
+       // put object
+       start := time.Now().UnixNano() / 1000 / 1000
+       err = bucket.PutObject(objectName, fd, TrafficLimitHeader(traffic), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       endingTime := time.Now().UnixNano() / 1000 / 1000
+       costT := endingTime - start
+       costV := contentLength * 1000 / costT // bit * 1000 / Millisecond = bit/s
+       c.Assert((costV < maxTraffic), Equals, true)
+       qosDelayTime = GetQosDelayTime(respHeader)
+       c.Assert(len(qosDelayTime) > 0, Equals, true)
+
+       // putobject without TrafficLimit
+       //
+       // fd, err = os.Open(localFile)
+       // c.Assert(err, IsNil)
+       // defer fd.Close()
+       // start = time.Now().UnixNano() / 1000 / 1000
+       // err = bucket.PutObject(objectName, fd)
+       // c.Assert(err, IsNil)
+       // endingTime = time.Now().UnixNano() / 1000 / 1000
+       // costT = endingTime - start
+       // costV = contentLength * 1000 / costT  // bit * 1000 / Millisecond = bit/s
+       // testLogger.Println(traffic, maxTraffic, contentLength, costT, costV)
+       // c.Assert((costV < maxTraffic), Equals, true)
+
+       // get object to file
+       newFile := "test-file-" + RandStr(10)
+       start = time.Now().UnixNano() / 1000 / 1000
+       err = bucket.GetObjectToFile(objectName, newFile, TrafficLimitHeader(traffic))
+       c.Assert(err, IsNil)
+       endingTime = time.Now().UnixNano() / 1000 / 1000
+       costT = endingTime - start
+       costV = contentLength * 1000 / costT // bit * 1000 / Millisecond = bit/s
+       c.Assert((costV < maxTraffic), Equals, true)
+       os.Remove(newFile)
+
+       // append object
+       newFile = "test-file-" + RandStr(10)
+       objectKey := objectNamePrefix + RandStr(8)
+       var nextPos int64
+       fd, err = os.Open(localFile)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+       start = time.Now().UnixNano() / 1000 / 1000
+       nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(RandStr(18)), nextPos)
+       c.Assert(err, IsNil)
+
+       var respAppendHeader http.Header
+       nextPos, err = bucket.AppendObject(objectKey, fd, nextPos, TrafficLimitHeader(traffic), GetResponseHeader(&respAppendHeader))
+       c.Assert(err, IsNil)
+       endingTime = time.Now().UnixNano() / 1000 / 1000
+       costT = endingTime - start
+       costV = contentLength * 1000 / costT // bit * 1000 / Millisecond = bit/s
+       c.Assert((costV < maxTraffic), Equals, true)
+       qosDelayTime = GetQosDelayTime(respAppendHeader)
+       c.Assert(len(qosDelayTime) > 0, Equals, true)
+
+       err = bucket.GetObjectToFile(objectKey, newFile, TrafficLimitHeader(traffic))
+       c.Assert(err, IsNil)
+       err = bucket.DeleteObject(objectKey)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // put object with url
+       fd, err = os.Open(localFile)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+       strURL, err := bucket.SignURL(objectName, HTTPPut, 60, TrafficLimitParam(traffic))
+       start = time.Now().UnixNano() / 1000 / 1000
+       err = bucket.PutObjectWithURL(strURL, fd)
+       c.Assert(err, IsNil)
+       endingTime = time.Now().UnixNano() / 1000 / 1000
+       costT = endingTime - start
+       costV = contentLength * 1000 / costT // bit * 1000 / Millisecond = bit/s
+       c.Assert((costV < maxTraffic), Equals, true)
+
+       // get object with url
+       newFile = "test-file-" + RandStr(10)
+       strURL, err = bucket.SignURL(objectName, HTTPGet, 60, TrafficLimitParam(traffic))
+       c.Assert(err, IsNil)
+       start = time.Now().UnixNano() / 1000 / 1000
+       err = bucket.GetObjectToFileWithURL(strURL, newFile)
+       c.Assert(err, IsNil)
+       endingTime = time.Now().UnixNano() / 1000 / 1000
+       costT = endingTime - start
+       costV = contentLength * 1000 / costT // bit * 1000 / Millisecond = bit/s
+       c.Assert((costV < maxTraffic), Equals, true)
+       os.Remove(newFile)
+
+       // copy object
+       destObjectName := objectNamePrefix + RandStr(8)
+       _, err = bucket.CopyObject(objectName, destObjectName, TrafficLimitHeader(traffic))
+       c.Assert(err, IsNil)
+       err = bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestBucketTrafficLimitUpload(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       var traffic int64 = 819220 // 100KB
+       maxTraffic := traffic * 120 / 100
+       contentLength := 500 * 1024
+
+       var fileName = "test-file-" + RandStr(8)
+       objectName := objectNamePrefix + RandStr(8)
+       content := RandStr(contentLength)
+       CreateFile(fileName, content, c)
+
+       chunks, err := SplitFileByPartNum(fileName, 3)
+       c.Assert(err, IsNil)
+
+       options := []Option{
+               Expires(futureDate), Meta("my", "myprop"),
+       }
+
+       fd, err := os.Open(fileName)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       imur, err := bucket.InitiateMultipartUpload(objectName, options...)
+       c.Assert(err, IsNil)
+       var parts []UploadPart
+       start := time.Now().UnixNano() / 1000 / 1000
+       for _, chunk := range chunks {
+               fd.Seek(chunk.Offset, os.SEEK_SET)
+               part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number, TrafficLimitHeader(traffic))
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+       _, err = bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+       endingTime := time.Now().UnixNano() / 1000 / 1000
+       costT := endingTime - start
+       costV := int64(contentLength) * 8 * 1000 / costT // B * 8 * 1000 / Millisecond = bit/s
+       c.Assert((costV < maxTraffic), Equals, true)
+       os.Remove(fileName)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestPutObjectWithForbidOverWrite(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       contentLength := 1024
+       objectName := objectNamePrefix + RandStr(8)
+       content := RandStr(contentLength)
+
+       // first put success
+       err = bucket.PutObject(objectName, strings.NewReader(content), ForbidOverWrite(true))
+       c.Assert(err, IsNil)
+
+       // second put failure with ForbidOverWrite true
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(content), ForbidOverWrite(true), GetResponseHeader(&respHeader))
+       c.Assert(err, NotNil)
+
+       // third  put success with ForbidOverWrite false
+       err = bucket.PutObject(objectName, strings.NewReader(content), ForbidOverWrite(false))
+       c.Assert(err, IsNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestCopyObjectWithForbidOverWrite(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       contentLength := 1024
+       objectName := objectNamePrefix + RandStr(8)
+       content := RandStr(contentLength)
+
+       err = bucket.PutObject(objectName, strings.NewReader(content))
+       c.Assert(err, IsNil)
+
+       // first copy success
+       copyObjectName := objectName + "-copy"
+       _, err = bucket.CopyObject(objectName, copyObjectName, ForbidOverWrite(true))
+       c.Assert(err, IsNil)
+
+       // second copy failure with ForbidOverWrite true
+       var respHeader http.Header
+       _, err = bucket.CopyObject(objectName, copyObjectName, ForbidOverWrite(true), GetResponseHeader(&respHeader))
+       c.Assert(err, NotNil)
+
+       // third  copy success with ForbidOverWrite false
+       _, err = bucket.CopyObject(objectName, copyObjectName, ForbidOverWrite(false))
+       c.Assert(err, IsNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestDeleteObjectsWithSpecialCharacter(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       contentLength := 100
+       objectName1 := objectNamePrefix + RandStr(8) + "<-->+&*\r%%"
+       objectName2 := objectNamePrefix + RandStr(8) + "\r&*\r%%"
+       //objectName2 := objectNamePrefix + RandStr(8) + "%C0%AE%C0%AE%2F%C0%AE%C0%AE%2F%C0%AE%C0%AE%2F%C0%AE%C0%AE%2F%C0%AE%C0%AE%2F%C0%AE%C0%AE%2F%C0%AE%C0%AE%2F%C0%AE%C0%AE%2F%C0%AE%C0%AE%2F%C0%AE%C0%AE%2Fetc%2Fprofile"
+       //objectName2, err = url.QueryUnescape(objectName2)
+
+       c.Assert(err, IsNil)
+       content := RandStr(contentLength)
+
+       err = bucket.PutObject(objectName1, strings.NewReader(content))
+       c.Assert(err, IsNil)
+
+       err = bucket.PutObject(objectName2, strings.NewReader(content))
+       c.Assert(err, IsNil)
+
+       // delete objectName1 objectName2
+       objectKeys := []string{objectName1, objectName2}
+       _, err = bucket.DeleteObjects(objectKeys)
+       c.Assert(err, IsNil)
+
+       // objectName1 is not exist
+       exist, err := bucket.IsObjectExist(objectName1)
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, false)
+
+       // objectName2 is not exist
+       exist, err = bucket.IsObjectExist(objectName2)
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, false)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// TestGetObjectRangeBehavior
+func (s *OssBucketSuite) TestGetObjectRangeBehavior(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectLen := 1000
+       objectValue := RandStr(objectLen)
+
+       // Put
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Range 1
+       options := []Option{
+               RangeBehavior("standard"),
+               Range(1000, 2000),
+       }
+       resp, err := bucket.GetObject(objectName, options...)
+       c.Assert(resp, IsNil)
+       c.Assert(err.(ServiceError).StatusCode, Equals, 416)
+
+       // Range 2
+       options = []Option{
+               RangeBehavior("standard"),
+               Range(0, 2000),
+       }
+       resp, err = bucket.GetObject(objectName, options...)
+       c.Assert(err, IsNil)
+       data, err := ioutil.ReadAll(resp)
+       resp.Close()
+       str := string(data)
+       c.Assert(len(str), Equals, 1000)
+       c.Assert(resp.(*Response).StatusCode, Equals, 206)
+
+       // Range 3
+       options = []Option{
+               RangeBehavior("standard"),
+               Range(500, 2000),
+       }
+       resp, err = bucket.GetObject(objectName, options...)
+       c.Assert(err, IsNil)
+       data, err = ioutil.ReadAll(resp)
+       resp.Close()
+       str = string(data)
+       c.Assert(len(str), Equals, 500)
+       c.Assert(resp.(*Response).StatusCode, Equals, 206)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// RangeBehavior  is an option to set Range value, such as "standard"
+func MyRangeBehavior(value string) Option {
+       return SetHeader(HTTPHeaderOssRangeBehavior, value)
+}
+
+// TestUserSetHeader
+func (s *OssBucketSuite) TestSupportUserSetHeader(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectLen := 1000
+       objectValue := RandStr(objectLen)
+
+       // Put
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Range 1
+       options := []Option{
+               MyRangeBehavior("standard"),
+               Range(1000, 2000),
+       }
+       resp, err := bucket.GetObject(objectName, options...)
+       c.Assert(resp, IsNil)
+       c.Assert(err.(ServiceError).StatusCode, Equals, 416)
+
+       // Range 2
+       options = []Option{
+               MyRangeBehavior("standard"),
+               Range(0, 2000),
+       }
+       resp, err = bucket.GetObject(objectName, options...)
+       c.Assert(err, IsNil)
+       data, err := ioutil.ReadAll(resp)
+       resp.Close()
+       str := string(data)
+       c.Assert(len(str), Equals, 1000)
+       c.Assert(resp.(*Response).StatusCode, Equals, 206)
+
+       // Range 3
+       options = []Option{
+               MyRangeBehavior("standard"),
+               Range(500, 2000),
+       }
+       resp, err = bucket.GetObject(objectName, options...)
+       c.Assert(err, IsNil)
+       data, err = ioutil.ReadAll(resp)
+       resp.Close()
+       str = string(data)
+       c.Assert(len(str), Equals, 500)
+       c.Assert(resp.(*Response).StatusCode, Equals, 206)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// user can set param
+func MyVersionId(value string) Option {
+       return AddParam("versionId", value)
+}
+
+func (s *OssBucketSuite) TestSupportUserSetParam(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+       // put object v1
+       objectName := objectNamePrefix + RandStr(8)
+       contextV1 := RandStr(100)
+       versionIdV1 := ""
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV1 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV1) > 0, Equals, true)
+
+       // put object v2
+       contextV2 := RandStr(200)
+       versionIdV2 := ""
+       err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       versionIdV2 = GetVersionId(respHeader)
+       c.Assert(len(versionIdV2) > 0, Equals, true)
+
+       // check v1 and v2
+       c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+       // get object v1
+       body, err := bucket.GetObject(objectName, MyVersionId(versionIdV1))
+       c.Assert(err, IsNil)
+       str, err := readBody(body)
+       c.Assert(err, IsNil)
+       body.Close()
+       c.Assert(str, Equals, contextV1)
+
+       // get object v2
+       body, err = bucket.GetObject(objectName, MyVersionId(versionIdV2))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       body.Close()
+       c.Assert(str, Equals, contextV2)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestPutObjectWithKmsSm4(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(1024)
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), ServerSideEncryption("KMS"), ServerSideDataEncryption("SM4"))
+       headers, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(headers.Get(HTTPHeaderOssServerSideEncryption), Equals, "KMS")
+       c.Assert(headers.Get(HTTPHeaderOssServerSideDataEncryption), Equals, "SM4")
+       c.Assert(err, IsNil)
+       ForceDeleteBucket(client, bucketName, c)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client.go
new file mode 100644 (file)
index 0000000..fd13e4f
--- /dev/null
@@ -0,0 +1,1722 @@
+// Package oss implements functions for access oss service.
+// It has two main struct Client and Bucket.
+package oss
+
+import (
+       "bytes"
+       "encoding/xml"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "log"
+       "net"
+       "net/http"
+       "strings"
+       "time"
+)
+
+// Client SDK's entry point. It's for bucket related options such as create/delete/set bucket (such as set/get ACL/lifecycle/referer/logging/website).
+// Object related operations are done by Bucket class.
+// Users use oss.New to create Client instance.
+//
+type (
+       // Client OSS client
+       Client struct {
+               Config     *Config      // OSS client configuration
+               Conn       *Conn        // Send HTTP request
+               HTTPClient *http.Client //http.Client to use - if nil will make its own
+       }
+
+       // ClientOption client option such as UseCname, Timeout, SecurityToken.
+       ClientOption func(*Client)
+)
+
+// New creates a new client.
+//
+// endpoint    the OSS datacenter endpoint such as http://oss-cn-hangzhou.aliyuncs.com .
+// accessKeyId    access key Id.
+// accessKeySecret    access key secret.
+//
+// Client    creates the new client instance, the returned value is valid when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func New(endpoint, accessKeyID, accessKeySecret string, options ...ClientOption) (*Client, error) {
+       // Configuration
+       config := getDefaultOssConfig()
+       config.Endpoint = endpoint
+       config.AccessKeyID = accessKeyID
+       config.AccessKeySecret = accessKeySecret
+
+       // URL parse
+       url := &urlMaker{}
+       err := url.Init(config.Endpoint, config.IsCname, config.IsUseProxy)
+       if err != nil {
+               return nil, err
+       }
+
+       // HTTP connect
+       conn := &Conn{config: config, url: url}
+
+       // OSS client
+       client := &Client{
+               Config: config,
+               Conn:   conn,
+       }
+
+       // Client options parse
+       for _, option := range options {
+               option(client)
+       }
+
+       if config.AuthVersion != AuthV1 && config.AuthVersion != AuthV2 {
+               return nil, fmt.Errorf("Init client Error, invalid Auth version: %v", config.AuthVersion)
+       }
+
+       // Create HTTP connection
+       err = conn.init(config, url, client.HTTPClient)
+
+       return client, err
+}
+
+// Bucket gets the bucket instance.
+//
+// bucketName    the bucket name.
+// Bucket    the bucket object, when error is nil.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) Bucket(bucketName string) (*Bucket, error) {
+       err := CheckBucketName(bucketName)
+       if err != nil {
+               return nil, err
+       }
+
+       return &Bucket{
+               client,
+               bucketName,
+       }, nil
+}
+
+// CreateBucket creates a bucket.
+//
+// bucketName    the bucket name, it's globably unique and immutable. The bucket name can only consist of lowercase letters, numbers and dash ('-').
+//               It must start with lowercase letter or number and the length can only be between 3 and 255.
+// options    options for creating the bucket, with optional ACL. The ACL could be ACLPrivate, ACLPublicRead, and ACLPublicReadWrite. By default it's ACLPrivate.
+//            It could also be specified with StorageClass option, which supports StorageStandard, StorageIA(infrequent access), StorageArchive.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) CreateBucket(bucketName string, options ...Option) error {
+       headers := make(map[string]string)
+       handleOptions(headers, options)
+
+       buffer := new(bytes.Buffer)
+
+       var cbConfig createBucketConfiguration
+       cbConfig.StorageClass = StorageStandard
+
+       isStorageSet, valStroage, _ := IsOptionSet(options, storageClass)
+       isRedundancySet, valRedundancy, _ := IsOptionSet(options, redundancyType)
+       if isStorageSet {
+               cbConfig.StorageClass = valStroage.(StorageClassType)
+       }
+
+       if isRedundancySet {
+               cbConfig.DataRedundancyType = valRedundancy.(DataRedundancyType)
+       }
+
+       bs, err := xml.Marshal(cbConfig)
+       if err != nil {
+               return err
+       }
+       buffer.Write(bs)
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       resp, err := client.do("PUT", bucketName, params, headers, buffer, options...)
+       if err != nil {
+               return err
+       }
+
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// ListBuckets lists buckets of the current account under the given endpoint, with optional filters.
+//
+// options    specifies the filters such as Prefix, Marker and MaxKeys. Prefix is the bucket name's prefix filter.
+//            And marker makes sure the returned buckets' name are greater than it in lexicographic order.
+//            Maxkeys limits the max keys to return, and by default it's 100 and up to 1000.
+//            For the common usage scenario, please check out list_bucket.go in the sample.
+// ListBucketsResponse    the response object if error is nil.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) ListBuckets(options ...Option) (ListBucketsResult, error) {
+       var out ListBucketsResult
+
+       params, err := GetRawParams(options)
+       if err != nil {
+               return out, err
+       }
+
+       resp, err := client.do("GET", "", params, nil, nil, options...)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// IsBucketExist checks if the bucket exists
+//
+// bucketName    the bucket name.
+//
+// bool    true if it exists, and it's only valid when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) IsBucketExist(bucketName string) (bool, error) {
+       listRes, err := client.ListBuckets(Prefix(bucketName), MaxKeys(1))
+       if err != nil {
+               return false, err
+       }
+
+       if len(listRes.Buckets) == 1 && listRes.Buckets[0].Name == bucketName {
+               return true, nil
+       }
+       return false, nil
+}
+
+// DeleteBucket deletes the bucket. Only empty bucket can be deleted (no object and parts).
+//
+// bucketName    the bucket name.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) DeleteBucket(bucketName string, options ...Option) error {
+       params := map[string]interface{}{}
+       resp, err := client.do("DELETE", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return err
+       }
+
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// GetBucketLocation gets the bucket location.
+//
+// Checks out the following link for more information :
+// https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/endpoint.html
+//
+// bucketName    the bucket name
+//
+// string    bucket's datacenter location
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketLocation(bucketName string) (string, error) {
+       params := map[string]interface{}{}
+       params["location"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil)
+       if err != nil {
+               return "", err
+       }
+       defer resp.Body.Close()
+
+       var LocationConstraint string
+       err = xmlUnmarshal(resp.Body, &LocationConstraint)
+       return LocationConstraint, err
+}
+
+// SetBucketACL sets bucket's ACL.
+//
+// bucketName    the bucket name
+// bucketAcl    the bucket ACL: ACLPrivate, ACLPublicRead and ACLPublicReadWrite.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketACL(bucketName string, bucketACL ACLType) error {
+       headers := map[string]string{HTTPHeaderOssACL: string(bucketACL)}
+       params := map[string]interface{}{}
+       params["acl"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetBucketACL gets the bucket ACL.
+//
+// bucketName    the bucket name.
+//
+// GetBucketAclResponse    the result object, and it's only valid when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketACL(bucketName string) (GetBucketACLResult, error) {
+       var out GetBucketACLResult
+       params := map[string]interface{}{}
+       params["acl"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// SetBucketLifecycle sets the bucket's lifecycle.
+//
+// For more information, checks out following link:
+// https://help.aliyun.com/document_detail/oss/user_guide/manage_object/object_lifecycle.html
+//
+// bucketName    the bucket name.
+// rules    the lifecycle rules. There're two kind of rules: absolute time expiration and relative time expiration in days and day/month/year respectively.
+//          Check out sample/bucket_lifecycle.go for more details.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketLifecycle(bucketName string, rules []LifecycleRule) error {
+       err := verifyLifecycleRules(rules)
+       if err != nil {
+               return err
+       }
+       lifecycleCfg := LifecycleConfiguration{Rules: rules}
+       bs, err := xml.Marshal(lifecycleCfg)
+       if err != nil {
+               return err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := map[string]string{}
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["lifecycle"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, buffer)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// DeleteBucketLifecycle deletes the bucket's lifecycle.
+//
+//
+// bucketName    the bucket name.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) DeleteBucketLifecycle(bucketName string) error {
+       params := map[string]interface{}{}
+       params["lifecycle"] = nil
+       resp, err := client.do("DELETE", bucketName, params, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// GetBucketLifecycle gets the bucket's lifecycle settings.
+//
+// bucketName    the bucket name.
+//
+// GetBucketLifecycleResponse    the result object upon successful request. It's only valid when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketLifecycle(bucketName string) (GetBucketLifecycleResult, error) {
+       var out GetBucketLifecycleResult
+       params := map[string]interface{}{}
+       params["lifecycle"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+
+       // NonVersionTransition is not suggested to use
+       // to keep compatible
+       for k, rule := range out.Rules {
+               if len(rule.NonVersionTransitions) > 0 {
+                       out.Rules[k].NonVersionTransition = &(out.Rules[k].NonVersionTransitions[0])
+               }
+       }
+       return out, err
+}
+
+// SetBucketReferer sets the bucket's referer whitelist and the flag if allowing empty referrer.
+//
+// To avoid stealing link on OSS data, OSS supports the HTTP referrer header. A whitelist referrer could be set either by API or web console, as well as
+// the allowing empty referrer flag. Note that this applies to requests from webbrowser only.
+// For example, for a bucket os-example and its referrer http://www.aliyun.com, all requests from this URL could access the bucket.
+// For more information, please check out this link :
+// https://help.aliyun.com/document_detail/oss/user_guide/security_management/referer.html
+//
+// bucketName    the bucket name.
+// referers    the referrer white list. A bucket could have a referrer list and each referrer supports one '*' and multiple '?' as wildcards.
+//             The sample could be found in sample/bucket_referer.go
+// allowEmptyReferer    the flag of allowing empty referrer. By default it's true.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketReferer(bucketName string, referers []string, allowEmptyReferer bool) error {
+       rxml := RefererXML{}
+       rxml.AllowEmptyReferer = allowEmptyReferer
+       if referers == nil {
+               rxml.RefererList = append(rxml.RefererList, "")
+       } else {
+               for _, referer := range referers {
+                       rxml.RefererList = append(rxml.RefererList, referer)
+               }
+       }
+
+       bs, err := xml.Marshal(rxml)
+       if err != nil {
+               return err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := map[string]string{}
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["referer"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, buffer)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetBucketReferer gets the bucket's referrer white list.
+//
+// bucketName    the bucket name.
+//
+// GetBucketRefererResponse    the result object upon successful request. It's only valid when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketReferer(bucketName string) (GetBucketRefererResult, error) {
+       var out GetBucketRefererResult
+       params := map[string]interface{}{}
+       params["referer"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// SetBucketLogging sets the bucket logging settings.
+//
+// OSS could automatically store the access log. Only the bucket owner could enable the logging.
+// Once enabled, OSS would save all the access log into hourly log files in a specified bucket.
+// For more information, please check out https://help.aliyun.com/document_detail/oss/user_guide/security_management/logging.html
+//
+// bucketName    bucket name to enable the log.
+// targetBucket    the target bucket name to store the log files.
+// targetPrefix    the log files' prefix.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketLogging(bucketName, targetBucket, targetPrefix string,
+       isEnable bool) error {
+       var err error
+       var bs []byte
+       if isEnable {
+               lxml := LoggingXML{}
+               lxml.LoggingEnabled.TargetBucket = targetBucket
+               lxml.LoggingEnabled.TargetPrefix = targetPrefix
+               bs, err = xml.Marshal(lxml)
+       } else {
+               lxml := loggingXMLEmpty{}
+               bs, err = xml.Marshal(lxml)
+       }
+
+       if err != nil {
+               return err
+       }
+
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := map[string]string{}
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["logging"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, buffer)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// DeleteBucketLogging deletes the logging configuration to disable the logging on the bucket.
+//
+// bucketName    the bucket name to disable the logging.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) DeleteBucketLogging(bucketName string) error {
+       params := map[string]interface{}{}
+       params["logging"] = nil
+       resp, err := client.do("DELETE", bucketName, params, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// GetBucketLogging gets the bucket's logging settings
+//
+// bucketName    the bucket name
+// GetBucketLoggingResponse    the result object upon successful request. It's only valid when error is nil.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketLogging(bucketName string) (GetBucketLoggingResult, error) {
+       var out GetBucketLoggingResult
+       params := map[string]interface{}{}
+       params["logging"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// SetBucketWebsite sets the bucket's static website's index and error page.
+//
+// OSS supports static web site hosting for the bucket data. When the bucket is enabled with that, you can access the file in the bucket like the way to access a static website.
+// For more information, please check out: https://help.aliyun.com/document_detail/oss/user_guide/static_host_website.html
+//
+// bucketName    the bucket name to enable static web site.
+// indexDocument    index page.
+// errorDocument    error page.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketWebsite(bucketName, indexDocument, errorDocument string) error {
+       wxml := WebsiteXML{}
+       wxml.IndexDocument.Suffix = indexDocument
+       wxml.ErrorDocument.Key = errorDocument
+
+       bs, err := xml.Marshal(wxml)
+       if err != nil {
+               return err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := make(map[string]string)
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["website"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, buffer)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// SetBucketWebsiteDetail sets the bucket's static website's detail
+//
+// OSS supports static web site hosting for the bucket data. When the bucket is enabled with that, you can access the file in the bucket like the way to access a static website.
+// For more information, please check out: https://help.aliyun.com/document_detail/oss/user_guide/static_host_website.html
+//
+// bucketName the bucket name to enable static web site.
+//
+// wxml the website's detail
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketWebsiteDetail(bucketName string, wxml WebsiteXML, options ...Option) error {
+       bs, err := xml.Marshal(wxml)
+       if err != nil {
+               return err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := make(map[string]string)
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["website"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, buffer, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// SetBucketWebsiteXml sets the bucket's static website's rule
+//
+// OSS supports static web site hosting for the bucket data. When the bucket is enabled with that, you can access the file in the bucket like the way to access a static website.
+// For more information, please check out: https://help.aliyun.com/document_detail/oss/user_guide/static_host_website.html
+//
+// bucketName the bucket name to enable static web site.
+//
+// wxml the website's detail
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketWebsiteXml(bucketName string, webXml string, options ...Option) error {
+       buffer := new(bytes.Buffer)
+       buffer.Write([]byte(webXml))
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := make(map[string]string)
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["website"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, buffer, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// DeleteBucketWebsite deletes the bucket's static web site settings.
+//
+// bucketName    the bucket name.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) DeleteBucketWebsite(bucketName string) error {
+       params := map[string]interface{}{}
+       params["website"] = nil
+       resp, err := client.do("DELETE", bucketName, params, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// GetBucketWebsite gets the bucket's default page (index page) and the error page.
+//
+// bucketName    the bucket name
+//
+// GetBucketWebsiteResponse    the result object upon successful request. It's only valid when error is nil.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketWebsite(bucketName string) (GetBucketWebsiteResult, error) {
+       var out GetBucketWebsiteResult
+       params := map[string]interface{}{}
+       params["website"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// SetBucketCORS sets the bucket's CORS rules
+//
+// For more information, please check out https://help.aliyun.com/document_detail/oss/user_guide/security_management/cors.html
+//
+// bucketName    the bucket name
+// corsRules    the CORS rules to set. The related sample code is in sample/bucket_cors.go.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketCORS(bucketName string, corsRules []CORSRule) error {
+       corsxml := CORSXML{}
+       for _, v := range corsRules {
+               cr := CORSRule{}
+               cr.AllowedMethod = v.AllowedMethod
+               cr.AllowedOrigin = v.AllowedOrigin
+               cr.AllowedHeader = v.AllowedHeader
+               cr.ExposeHeader = v.ExposeHeader
+               cr.MaxAgeSeconds = v.MaxAgeSeconds
+               corsxml.CORSRules = append(corsxml.CORSRules, cr)
+       }
+
+       bs, err := xml.Marshal(corsxml)
+       if err != nil {
+               return err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := map[string]string{}
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["cors"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, buffer)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// DeleteBucketCORS deletes the bucket's static website settings.
+//
+// bucketName    the bucket name.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) DeleteBucketCORS(bucketName string) error {
+       params := map[string]interface{}{}
+       params["cors"] = nil
+       resp, err := client.do("DELETE", bucketName, params, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// GetBucketCORS gets the bucket's CORS settings.
+//
+// bucketName    the bucket name.
+// GetBucketCORSResult    the result object upon successful request. It's only valid when error is nil.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketCORS(bucketName string) (GetBucketCORSResult, error) {
+       var out GetBucketCORSResult
+       params := map[string]interface{}{}
+       params["cors"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// GetBucketInfo gets the bucket information.
+//
+// bucketName    the bucket name.
+// GetBucketInfoResult    the result object upon successful request. It's only valid when error is nil.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketInfo(bucketName string, options ...Option) (GetBucketInfoResult, error) {
+       var out GetBucketInfoResult
+       params := map[string]interface{}{}
+       params["bucketInfo"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+
+       // convert None to ""
+       if err == nil {
+               if out.BucketInfo.SseRule.KMSMasterKeyID == "None" {
+                       out.BucketInfo.SseRule.KMSMasterKeyID = ""
+               }
+
+               if out.BucketInfo.SseRule.SSEAlgorithm == "None" {
+                       out.BucketInfo.SseRule.SSEAlgorithm = ""
+               }
+
+               if out.BucketInfo.SseRule.KMSDataEncryption == "None" {
+                       out.BucketInfo.SseRule.KMSDataEncryption = ""
+               }
+       }
+       return out, err
+}
+
+// SetBucketVersioning set bucket versioning:Enabled、Suspended
+// bucketName    the bucket name.
+// error    it's nil if no error, otherwise it's an error object.
+func (client Client) SetBucketVersioning(bucketName string, versioningConfig VersioningConfig, options ...Option) error {
+       var err error
+       var bs []byte
+       bs, err = xml.Marshal(versioningConfig)
+
+       if err != nil {
+               return err
+       }
+
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := map[string]string{}
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["versioning"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, buffer, options...)
+
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetBucketVersioning get bucket versioning status:Enabled、Suspended
+// bucketName    the bucket name.
+// error    it's nil if no error, otherwise it's an error object.
+func (client Client) GetBucketVersioning(bucketName string, options ...Option) (GetBucketVersioningResult, error) {
+       var out GetBucketVersioningResult
+       params := map[string]interface{}{}
+       params["versioning"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil, options...)
+
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// SetBucketEncryption set bucket encryption config
+// bucketName    the bucket name.
+// error    it's nil if no error, otherwise it's an error object.
+func (client Client) SetBucketEncryption(bucketName string, encryptionRule ServerEncryptionRule, options ...Option) error {
+       var err error
+       var bs []byte
+       bs, err = xml.Marshal(encryptionRule)
+
+       if err != nil {
+               return err
+       }
+
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := map[string]string{}
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["encryption"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, buffer, options...)
+
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetBucketEncryption get bucket encryption
+// bucketName    the bucket name.
+// error    it's nil if no error, otherwise it's an error object.
+func (client Client) GetBucketEncryption(bucketName string, options ...Option) (GetBucketEncryptionResult, error) {
+       var out GetBucketEncryptionResult
+       params := map[string]interface{}{}
+       params["encryption"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil, options...)
+
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// DeleteBucketEncryption delete bucket encryption config
+// bucketName    the bucket name.
+// error    it's nil if no error, otherwise it's an error bucket
+func (client Client) DeleteBucketEncryption(bucketName string, options ...Option) error {
+       params := map[string]interface{}{}
+       params["encryption"] = nil
+       resp, err := client.do("DELETE", bucketName, params, nil, nil, options...)
+
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+//
+// SetBucketTagging add tagging to bucket
+// bucketName  name of bucket
+// tagging    tagging to be added
+// error        nil if success, otherwise error
+func (client Client) SetBucketTagging(bucketName string, tagging Tagging, options ...Option) error {
+       var err error
+       var bs []byte
+       bs, err = xml.Marshal(tagging)
+
+       if err != nil {
+               return err
+       }
+
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := map[string]string{}
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["tagging"] = nil
+       resp, err := client.do("PUT", bucketName, params, headers, buffer, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetBucketTagging get tagging of the bucket
+// bucketName  name of bucket
+// error      nil if success, otherwise error
+func (client Client) GetBucketTagging(bucketName string, options ...Option) (GetBucketTaggingResult, error) {
+       var out GetBucketTaggingResult
+       params := map[string]interface{}{}
+       params["tagging"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+//
+// DeleteBucketTagging delete bucket tagging
+// bucketName  name of bucket
+// error      nil if success, otherwise error
+//
+func (client Client) DeleteBucketTagging(bucketName string, options ...Option) error {
+       params := map[string]interface{}{}
+       params["tagging"] = nil
+       resp, err := client.do("DELETE", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// GetBucketStat get bucket stat
+// bucketName    the bucket name.
+// error    it's nil if no error, otherwise it's an error object.
+func (client Client) GetBucketStat(bucketName string) (GetBucketStatResult, error) {
+       var out GetBucketStatResult
+       params := map[string]interface{}{}
+       params["stat"] = nil
+       resp, err := client.do("GET", bucketName, params, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// GetBucketPolicy API operation for Object Storage Service.
+//
+// Get the policy from the bucket.
+//
+// bucketName   the bucket name.
+//
+// string               return the bucket's policy, and it's only valid when error is nil.
+//
+// error                it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketPolicy(bucketName string, options ...Option) (string, error) {
+       params := map[string]interface{}{}
+       params["policy"] = nil
+
+       resp, err := client.do("GET", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return "", err
+       }
+       defer resp.Body.Close()
+
+       body, err := ioutil.ReadAll(resp.Body)
+
+       out := string(body)
+       return out, err
+}
+
+// SetBucketPolicy API operation for Object Storage Service.
+//
+// Set the policy from the bucket.
+//
+// bucketName the bucket name.
+//
+// policy the bucket policy.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketPolicy(bucketName string, policy string, options ...Option) error {
+       params := map[string]interface{}{}
+       params["policy"] = nil
+
+       buffer := strings.NewReader(policy)
+
+       resp, err := client.do("PUT", bucketName, params, nil, buffer, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// DeleteBucketPolicy API operation for Object Storage Service.
+//
+// Deletes the policy from the bucket.
+//
+// bucketName the bucket name.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) DeleteBucketPolicy(bucketName string, options ...Option) error {
+       params := map[string]interface{}{}
+       params["policy"] = nil
+       resp, err := client.do("DELETE", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return err
+       }
+
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// SetBucketRequestPayment API operation for Object Storage Service.
+//
+// Set the requestPayment of bucket
+//
+// bucketName the bucket name.
+//
+// paymentConfig the payment configuration
+//
+// error it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketRequestPayment(bucketName string, paymentConfig RequestPaymentConfiguration, options ...Option) error {
+       params := map[string]interface{}{}
+       params["requestPayment"] = nil
+
+       var bs []byte
+       bs, err := xml.Marshal(paymentConfig)
+
+       if err != nil {
+               return err
+       }
+
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := map[string]string{}
+       headers[HTTPHeaderContentType] = contentType
+
+       resp, err := client.do("PUT", bucketName, params, headers, buffer, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetBucketRequestPayment API operation for Object Storage Service.
+//
+// Get bucket requestPayment
+//
+// bucketName the bucket name.
+//
+// RequestPaymentConfiguration the payment configuration
+//
+// error it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketRequestPayment(bucketName string, options ...Option) (RequestPaymentConfiguration, error) {
+       var out RequestPaymentConfiguration
+       params := map[string]interface{}{}
+       params["requestPayment"] = nil
+
+       resp, err := client.do("GET", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// GetUserQoSInfo API operation for Object Storage Service.
+//
+// Get user qos.
+//
+// UserQoSConfiguration the User Qos and range Information.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetUserQoSInfo(options ...Option) (UserQoSConfiguration, error) {
+       var out UserQoSConfiguration
+       params := map[string]interface{}{}
+       params["qosInfo"] = nil
+
+       resp, err := client.do("GET", "", params, nil, nil, options...)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// SetBucketQoSInfo API operation for Object Storage Service.
+//
+// Set Bucket Qos information.
+//
+// bucketName the bucket name.
+//
+// qosConf the qos configuration.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) SetBucketQoSInfo(bucketName string, qosConf BucketQoSConfiguration, options ...Option) error {
+       params := map[string]interface{}{}
+       params["qosInfo"] = nil
+
+       var bs []byte
+       bs, err := xml.Marshal(qosConf)
+       if err != nil {
+               return err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentTpye := http.DetectContentType(buffer.Bytes())
+       headers := map[string]string{}
+       headers[HTTPHeaderContentType] = contentTpye
+
+       resp, err := client.do("PUT", bucketName, params, headers, buffer, options...)
+       if err != nil {
+               return err
+       }
+
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetBucketQosInfo API operation for Object Storage Service.
+//
+// Get Bucket Qos information.
+//
+// bucketName the bucket name.
+//
+// BucketQoSConfiguration the  return qos configuration.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketQosInfo(bucketName string, options ...Option) (BucketQoSConfiguration, error) {
+       var out BucketQoSConfiguration
+       params := map[string]interface{}{}
+       params["qosInfo"] = nil
+
+       resp, err := client.do("GET", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// DeleteBucketQosInfo API operation for Object Storage Service.
+//
+// Delete Bucket QoS information.
+//
+// bucketName the bucket name.
+//
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) DeleteBucketQosInfo(bucketName string, options ...Option) error {
+       params := map[string]interface{}{}
+       params["qosInfo"] = nil
+
+       resp, err := client.do("DELETE", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// SetBucketInventory API operation for Object Storage Service
+//
+// Set the Bucket inventory.
+//
+// bucketName tht bucket name.
+//
+// inventoryConfig the inventory configuration.
+//
+// error    it's nil if no error, otherwise it's an error.
+//
+func (client Client) SetBucketInventory(bucketName string, inventoryConfig InventoryConfiguration, options ...Option) error {
+       params := map[string]interface{}{}
+       params["inventoryId"] = inventoryConfig.Id
+       params["inventory"] = nil
+
+       var bs []byte
+       bs, err := xml.Marshal(inventoryConfig)
+
+       if err != nil {
+               return err
+       }
+
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := make(map[string]string)
+       headers[HTTPHeaderContentType] = contentType
+
+       resp, err := client.do("PUT", bucketName, params, headers, buffer, options...)
+
+       if err != nil {
+               return err
+       }
+
+       defer resp.Body.Close()
+
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetBucketInventory API operation for Object Storage Service
+//
+// Get the Bucket inventory.
+//
+// bucketName tht bucket name.
+//
+// strInventoryId the inventory id.
+//
+// InventoryConfiguration the inventory configuration.
+//
+// error    it's nil if no error, otherwise it's an error.
+//
+func (client Client) GetBucketInventory(bucketName string, strInventoryId string, options ...Option) (InventoryConfiguration, error) {
+       var out InventoryConfiguration
+       params := map[string]interface{}{}
+       params["inventory"] = nil
+       params["inventoryId"] = strInventoryId
+
+       resp, err := client.do("GET", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// ListBucketInventory API operation for Object Storage Service
+//
+// List the Bucket inventory.
+//
+// bucketName tht bucket name.
+//
+// continuationToken the users token.
+//
+// ListInventoryConfigurationsResult list all inventory configuration by .
+//
+// error    it's nil if no error, otherwise it's an error.
+//
+func (client Client) ListBucketInventory(bucketName, continuationToken string, options ...Option) (ListInventoryConfigurationsResult, error) {
+       var out ListInventoryConfigurationsResult
+       params := map[string]interface{}{}
+       params["inventory"] = nil
+       if continuationToken == "" {
+               params["continuation-token"] = nil
+       } else {
+               params["continuation-token"] = continuationToken
+       }
+
+       resp, err := client.do("GET", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// DeleteBucketInventory API operation for Object Storage Service.
+//
+// Delete Bucket inventory information.
+//
+// bucketName tht bucket name.
+//
+// strInventoryId the inventory id.
+//
+// error    it's nil if no error, otherwise it's an error.
+//
+func (client Client) DeleteBucketInventory(bucketName, strInventoryId string, options ...Option) error {
+       params := map[string]interface{}{}
+       params["inventory"] = nil
+       params["inventoryId"] = strInventoryId
+
+       resp, err := client.do("DELETE", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// SetBucketAsyncTask API operation for set async fetch task
+//
+// bucketName tht bucket name.
+//
+// asynConf  configruation
+//
+// error  it's nil if success, otherwise it's an error.
+func (client Client) SetBucketAsyncTask(bucketName string, asynConf AsyncFetchTaskConfiguration, options ...Option) (AsyncFetchTaskResult, error) {
+       var out AsyncFetchTaskResult
+       params := map[string]interface{}{}
+       params["asyncFetch"] = nil
+
+       var bs []byte
+       bs, err := xml.Marshal(asynConf)
+
+       if err != nil {
+               return out, err
+       }
+
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := make(map[string]string)
+       headers[HTTPHeaderContentType] = contentType
+
+       resp, err := client.do("POST", bucketName, params, headers, buffer, options...)
+
+       if err != nil {
+               return out, err
+       }
+
+       defer resp.Body.Close()
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// GetBucketAsyncTask API operation for set async fetch task
+//
+// bucketName tht bucket name.
+//
+// taskid  returned by SetBucketAsyncTask
+//
+// error  it's nil if success, otherwise it's an error.
+func (client Client) GetBucketAsyncTask(bucketName string, taskID string, options ...Option) (AsynFetchTaskInfo, error) {
+       var out AsynFetchTaskInfo
+       params := map[string]interface{}{}
+       params["asyncFetch"] = nil
+
+       headers := make(map[string]string)
+       headers[HTTPHeaderOssTaskID] = taskID
+       resp, err := client.do("GET", bucketName, params, headers, nil, options...)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// InitiateBucketWorm creates bucket worm Configuration
+// bucketName the bucket name.
+// retentionDays the retention period in days
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) InitiateBucketWorm(bucketName string, retentionDays int, options ...Option) (string, error) {
+       var initiateWormConf InitiateWormConfiguration
+       initiateWormConf.RetentionPeriodInDays = retentionDays
+
+       var respHeader http.Header
+       isOptSet, _, _ := IsOptionSet(options, responseHeader)
+       if !isOptSet {
+               options = append(options, GetResponseHeader(&respHeader))
+       }
+
+       bs, err := xml.Marshal(initiateWormConf)
+       if err != nil {
+               return "", err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := make(map[string]string)
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["worm"] = nil
+
+       resp, err := client.do("POST", bucketName, params, headers, buffer, options...)
+       if err != nil {
+               return "", err
+       }
+       defer resp.Body.Close()
+
+       respOpt, _ := FindOption(options, responseHeader, nil)
+       wormID := ""
+       err = CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+       if err == nil && respOpt != nil {
+               wormID = (respOpt.(*http.Header)).Get("x-oss-worm-id")
+       }
+       return wormID, err
+}
+
+// AbortBucketWorm delete bucket worm Configuration
+// bucketName the bucket name.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) AbortBucketWorm(bucketName string, options ...Option) error {
+       params := map[string]interface{}{}
+       params["worm"] = nil
+       resp, err := client.do("DELETE", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// CompleteBucketWorm complete bucket worm Configuration
+// bucketName the bucket name.
+// wormID the worm id
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) CompleteBucketWorm(bucketName string, wormID string, options ...Option) error {
+       params := map[string]interface{}{}
+       params["wormId"] = wormID
+       resp, err := client.do("POST", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// ExtendBucketWorm exetend bucket worm Configuration
+// bucketName the bucket name.
+// retentionDays the retention period in days
+// wormID the worm id
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) ExtendBucketWorm(bucketName string, retentionDays int, wormID string, options ...Option) error {
+       var extendWormConf ExtendWormConfiguration
+       extendWormConf.RetentionPeriodInDays = retentionDays
+
+       bs, err := xml.Marshal(extendWormConf)
+       if err != nil {
+               return err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       contentType := http.DetectContentType(buffer.Bytes())
+       headers := make(map[string]string)
+       headers[HTTPHeaderContentType] = contentType
+
+       params := map[string]interface{}{}
+       params["wormId"] = wormID
+       params["wormExtend"] = nil
+
+       resp, err := client.do("POST", bucketName, params, headers, buffer, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetBucketWorm get bucket worm Configuration
+// bucketName the bucket name.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (client Client) GetBucketWorm(bucketName string, options ...Option) (WormConfiguration, error) {
+       var out WormConfiguration
+       params := map[string]interface{}{}
+       params["worm"] = nil
+
+       resp, err := client.do("GET", bucketName, params, nil, nil, options...)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// LimitUploadSpeed set upload bandwidth limit speed,default is 0,unlimited
+// upSpeed KB/s, 0 is unlimited,default is 0
+// error it's nil if success, otherwise failure
+func (client Client) LimitUploadSpeed(upSpeed int) error {
+       if client.Config == nil {
+               return fmt.Errorf("client config is nil")
+       }
+       return client.Config.LimitUploadSpeed(upSpeed)
+}
+
+// UseCname sets the flag of using CName. By default it's false.
+//
+// isUseCname    true: the endpoint has the CName, false: the endpoint does not have cname. Default is false.
+//
+func UseCname(isUseCname bool) ClientOption {
+       return func(client *Client) {
+               client.Config.IsCname = isUseCname
+               client.Conn.url.Init(client.Config.Endpoint, client.Config.IsCname, client.Config.IsUseProxy)
+       }
+}
+
+// Timeout sets the HTTP timeout in seconds.
+//
+// connectTimeoutSec    HTTP timeout in seconds. Default is 10 seconds. 0 means infinite (not recommended)
+// readWriteTimeout    HTTP read or write's timeout in seconds. Default is 20 seconds. 0 means infinite.
+//
+func Timeout(connectTimeoutSec, readWriteTimeout int64) ClientOption {
+       return func(client *Client) {
+               client.Config.HTTPTimeout.ConnectTimeout =
+                       time.Second * time.Duration(connectTimeoutSec)
+               client.Config.HTTPTimeout.ReadWriteTimeout =
+                       time.Second * time.Duration(readWriteTimeout)
+               client.Config.HTTPTimeout.HeaderTimeout =
+                       time.Second * time.Duration(readWriteTimeout)
+               client.Config.HTTPTimeout.IdleConnTimeout =
+                       time.Second * time.Duration(readWriteTimeout)
+               client.Config.HTTPTimeout.LongTimeout =
+                       time.Second * time.Duration(readWriteTimeout*10)
+       }
+}
+
+// SecurityToken sets the temporary user's SecurityToken.
+//
+// token    STS token
+//
+func SecurityToken(token string) ClientOption {
+       return func(client *Client) {
+               client.Config.SecurityToken = strings.TrimSpace(token)
+       }
+}
+
+// EnableMD5 enables MD5 validation.
+//
+// isEnableMD5    true: enable MD5 validation; false: disable MD5 validation.
+//
+func EnableMD5(isEnableMD5 bool) ClientOption {
+       return func(client *Client) {
+               client.Config.IsEnableMD5 = isEnableMD5
+       }
+}
+
+// MD5ThresholdCalcInMemory sets the memory usage threshold for computing the MD5, default is 16MB.
+//
+// threshold    the memory threshold in bytes. When the uploaded content is more than 16MB, the temp file is used for computing the MD5.
+//
+func MD5ThresholdCalcInMemory(threshold int64) ClientOption {
+       return func(client *Client) {
+               client.Config.MD5Threshold = threshold
+       }
+}
+
+// EnableCRC enables the CRC checksum. Default is true.
+//
+// isEnableCRC    true: enable CRC checksum; false: disable the CRC checksum.
+//
+func EnableCRC(isEnableCRC bool) ClientOption {
+       return func(client *Client) {
+               client.Config.IsEnableCRC = isEnableCRC
+       }
+}
+
+// UserAgent specifies UserAgent. The default is aliyun-sdk-go/1.2.0 (windows/-/amd64;go1.5.2).
+//
+// userAgent    the user agent string.
+//
+func UserAgent(userAgent string) ClientOption {
+       return func(client *Client) {
+               client.Config.UserAgent = userAgent
+               client.Config.UserSetUa = true
+       }
+}
+
+// Proxy sets the proxy (optional). The default is not using proxy.
+//
+// proxyHost    the proxy host in the format "host:port". For example, proxy.com:80 .
+//
+func Proxy(proxyHost string) ClientOption {
+       return func(client *Client) {
+               client.Config.IsUseProxy = true
+               client.Config.ProxyHost = proxyHost
+               client.Conn.url.Init(client.Config.Endpoint, client.Config.IsCname, client.Config.IsUseProxy)
+       }
+}
+
+// AuthProxy sets the proxy information with user name and password.
+//
+// proxyHost    the proxy host in the format "host:port". For example, proxy.com:80 .
+// proxyUser    the proxy user name.
+// proxyPassword    the proxy password.
+//
+func AuthProxy(proxyHost, proxyUser, proxyPassword string) ClientOption {
+       return func(client *Client) {
+               client.Config.IsUseProxy = true
+               client.Config.ProxyHost = proxyHost
+               client.Config.IsAuthProxy = true
+               client.Config.ProxyUser = proxyUser
+               client.Config.ProxyPassword = proxyPassword
+               client.Conn.url.Init(client.Config.Endpoint, client.Config.IsCname, client.Config.IsUseProxy)
+       }
+}
+
+//
+// HTTPClient sets the http.Client in use to the one passed in
+//
+func HTTPClient(HTTPClient *http.Client) ClientOption {
+       return func(client *Client) {
+               client.HTTPClient = HTTPClient
+       }
+}
+
+//
+// SetLogLevel sets the oss sdk log level
+//
+func SetLogLevel(LogLevel int) ClientOption {
+       return func(client *Client) {
+               client.Config.LogLevel = LogLevel
+       }
+}
+
+//
+// SetLogger sets the oss sdk logger
+//
+func SetLogger(Logger *log.Logger) ClientOption {
+       return func(client *Client) {
+               client.Config.Logger = Logger
+       }
+}
+
+// SetCredentialsProvider sets funciton for get the user's ak
+func SetCredentialsProvider(provider CredentialsProvider) ClientOption {
+       return func(client *Client) {
+               client.Config.CredentialsProvider = provider
+       }
+}
+
+// SetLocalAddr sets funciton for local addr
+func SetLocalAddr(localAddr net.Addr) ClientOption {
+       return func(client *Client) {
+               client.Config.LocalAddr = localAddr
+       }
+}
+
+// AuthVersion  sets auth version: v1 or v2 signature which oss_server needed
+func AuthVersion(authVersion AuthVersionType) ClientOption {
+       return func(client *Client) {
+               client.Config.AuthVersion = authVersion
+       }
+}
+
+// AdditionalHeaders sets special http headers needed to be signed
+func AdditionalHeaders(headers []string) ClientOption {
+       return func(client *Client) {
+               client.Config.AdditionalHeaders = headers
+       }
+}
+
+// only effective from go1.7 onward,RedirectEnabled set http redirect enabled or not
+func RedirectEnabled(enabled bool) ClientOption {
+       return func(client *Client) {
+               client.Config.RedirectEnabled = enabled
+       }
+}
+
+// Private
+func (client Client) do(method, bucketName string, params map[string]interface{},
+       headers map[string]string, data io.Reader, options ...Option) (*Response, error) {
+       err := CheckBucketName(bucketName)
+       if len(bucketName) > 0 && err != nil {
+               return nil, err
+       }
+
+       // option headers
+       addHeaders := make(map[string]string)
+       err = handleOptions(addHeaders, options)
+       if err != nil {
+               return nil, err
+       }
+
+       // merge header
+       if headers == nil {
+               headers = make(map[string]string)
+       }
+
+       for k, v := range addHeaders {
+               if _, ok := headers[k]; !ok {
+                       headers[k] = v
+               }
+       }
+
+       resp, err := client.Conn.Do(method, bucketName, "", params, headers, data, 0, nil)
+
+       // get response header
+       respHeader, _ := FindOption(options, responseHeader, nil)
+       if respHeader != nil {
+               pRespHeader := respHeader.(*http.Header)
+               *pRespHeader = resp.Headers
+       }
+
+       return resp, err
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client_test.go
new file mode 100644 (file)
index 0000000..c2f4f7b
--- /dev/null
@@ -0,0 +1,3788 @@
+// client test
+// use gocheck, install gocheck to execute "go get gopkg.in/check.v1",
+// see https://labix.org/gocheck
+
+package oss
+
+import (
+       "encoding/json"
+       "encoding/xml"
+       "fmt"
+       "io/ioutil"
+       "log"
+       "math/rand"
+       "net"
+       "net/http"
+       "os"
+       "reflect"
+       "runtime"
+       "strconv"
+       "strings"
+       "testing"
+       "time"
+
+       . "gopkg.in/check.v1"
+)
+
+// Test hooks up gocheck into the "go test" runner.
+func Test(t *testing.T) {
+       TestingT(t)
+}
+
+type OssClientSuite struct{}
+
+var _ = Suite(&OssClientSuite{})
+
+var (
+       // Endpoint/ID/Key
+       endpoint  = os.Getenv("OSS_TEST_ENDPOINT")
+       accessID  = os.Getenv("OSS_TEST_ACCESS_KEY_ID")
+       accessKey = os.Getenv("OSS_TEST_ACCESS_KEY_SECRET")
+       accountID = os.Getenv("OSS_TEST_ACCOUNT_ID")
+
+       // Proxy
+       proxyHost   = os.Getenv("OSS_TEST_PROXY_HOST")
+       proxyUser   = os.Getenv("OSS_TEST_PROXY_USER")
+       proxyPasswd = os.Getenv("OSS_TEST_PROXY_PASSWORD")
+
+       // STS
+       stsaccessID  = os.Getenv("OSS_TEST_STS_ID")
+       stsaccessKey = os.Getenv("OSS_TEST_STS_KEY")
+       stsARN       = os.Getenv("OSS_TEST_STS_ARN")
+
+       // Credential
+       credentialAccessID  = os.Getenv("OSS_CREDENTIAL_KEY_ID")
+       credentialAccessKey = os.Getenv("OSS_CREDENTIAL_KEY_SECRET")
+       credentialUID       = os.Getenv("OSS_CREDENTIAL_UID")
+)
+
+var (
+       // prefix of bucket name for bucket ops test
+       bucketNamePrefix = "go-sdk-test-bucket-"
+       // bucket name for object ops test
+       bucketName        = bucketNamePrefix + RandLowStr(6)
+       archiveBucketName = bucketNamePrefix + "arch-" + RandLowStr(6)
+       // object name for object ops test
+       objectNamePrefix = "go-sdk-test-object-"
+       // sts region is one and only hangzhou
+       stsRegion = "cn-hangzhou"
+       // Credentials
+       credentialBucketName = bucketNamePrefix + RandLowStr(6)
+)
+
+var (
+       logPath            = "go_sdk_test_" + time.Now().Format("20060102_150405") + ".log"
+       testLogFile, _     = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE, 0664)
+       testLogger         = log.New(testLogFile, "", log.Ldate|log.Ltime|log.Lshortfile)
+       letters            = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+       timeoutInOperation = 3 * time.Second
+)
+
+func RandStr(n int) string {
+       b := make([]rune, n)
+       randMarker := rand.New(rand.NewSource(time.Now().UnixNano()))
+       for i := range b {
+               b[i] = letters[randMarker.Intn(len(letters))]
+       }
+       return string(b)
+}
+
+func CreateFile(fileName, content string, c *C) {
+       fout, err := os.Create(fileName)
+       defer fout.Close()
+       c.Assert(err, IsNil)
+       _, err = fout.WriteString(content)
+       c.Assert(err, IsNil)
+}
+
+func RandLowStr(n int) string {
+       return strings.ToLower(RandStr(n))
+}
+
+func ForceDeleteBucket(client *Client, bucketName string, c *C) {
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // Delete Object
+       marker := Marker("")
+       for {
+               lor, err := bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete Object Versions and DeleteMarks
+       keyMarker := KeyMarker("")
+       versionIdMarker := VersionIdMarker("")
+       options := []Option{keyMarker, versionIdMarker}
+       for {
+               lor, err := bucket.ListObjectVersions(options...)
+               if err != nil {
+                       break
+               }
+
+               for _, object := range lor.ObjectDeleteMarkers {
+                       err = bucket.DeleteObject(object.Key, VersionId(object.VersionId))
+                       c.Assert(err, IsNil)
+               }
+
+               for _, object := range lor.ObjectVersions {
+                       err = bucket.DeleteObject(object.Key, VersionId(object.VersionId))
+                       c.Assert(err, IsNil)
+               }
+
+               keyMarker = KeyMarker(lor.NextKeyMarker)
+               versionIdMarker := VersionIdMarker(lor.NextVersionIdMarker)
+               options = []Option{keyMarker, versionIdMarker}
+
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete Part
+       keyMarker = KeyMarker("")
+       uploadIDMarker := UploadIDMarker("")
+       for {
+               lmur, err := bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+               c.Assert(err, IsNil)
+               for _, upload := range lmur.Uploads {
+                       var imur = InitiateMultipartUploadResult{Bucket: bucketName,
+                               Key: upload.Key, UploadID: upload.UploadID}
+                       err = bucket.AbortMultipartUpload(imur)
+                       c.Assert(err, IsNil)
+               }
+               keyMarker = KeyMarker(lmur.NextKeyMarker)
+               uploadIDMarker = UploadIDMarker(lmur.NextUploadIDMarker)
+               if !lmur.IsTruncated {
+                       break
+               }
+       }
+
+       // delete live channel
+       strMarker := ""
+       for {
+               result, err := bucket.ListLiveChannel(Marker(strMarker))
+               c.Assert(err, IsNil)
+
+               for _, channel := range result.LiveChannel {
+                       err := bucket.DeleteLiveChannel(channel.Name)
+                       c.Assert(err, IsNil)
+               }
+
+               if result.IsTruncated {
+                       strMarker = result.NextMarker
+               } else {
+                       break
+               }
+       }
+
+       // Delete Bucket
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+// SetUpSuite runs once when the suite starts running
+func (s *OssClientSuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000))
+       c.Assert(err, IsNil)
+
+       for _, bucket := range lbr.Buckets {
+               ForceDeleteBucket(client, bucket.Name, c)
+       }
+
+       testLogger.Println("test client started")
+}
+
+// TearDownSuite runs before each test or benchmark starts running
+func (s *OssClientSuite) TearDownSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000))
+       c.Assert(err, IsNil)
+
+       for _, bucket := range lbr.Buckets {
+               s.deleteBucket(client, bucket.Name, c)
+       }
+
+       testLogger.Println("test client completed")
+}
+
+func (s *OssClientSuite) deleteBucket(client *Client, bucketName string, c *C) {
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// SetUpTest runs after each test or benchmark runs
+func (s *OssClientSuite) SetUpTest(c *C) {
+}
+
+// TearDownTest runs once after all tests or benchmarks have finished running
+func (s *OssClientSuite) TearDownTest(c *C) {
+}
+
+// TestCreateBucket
+func (s *OssClientSuite) TestCreateBucket(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Create
+       client.DeleteBucket(bucketNameTest)
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       //sleep 3 seconds after create bucket
+       time.Sleep(timeoutInOperation)
+
+       // verify bucket is exist
+       found, err := client.IsBucketExist(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(found, Equals, true)
+
+       res, err := client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // CreateBucket creates with ACLPublicRead
+       err = client.CreateBucket(bucketNameTest, ACL(ACLPublicRead))
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPublicRead))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // ACLPublicReadWrite
+       err = client.CreateBucket(bucketNameTest, ACL(ACLPublicReadWrite))
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPublicReadWrite))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // ACLPrivate
+       err = client.CreateBucket(bucketNameTest, ACL(ACLPrivate))
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+       // Delete
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Create bucket with configuration and test GetBucketInfo
+       for _, storage := range []StorageClassType{StorageStandard, StorageIA, StorageArchive, StorageColdArchive} {
+               bucketNameTest := bucketNamePrefix + RandLowStr(6)
+               err = client.CreateBucket(bucketNameTest, StorageClass(storage), ACL(ACLPublicRead))
+               c.Assert(err, IsNil)
+               time.Sleep(timeoutInOperation)
+
+               res, err := client.GetBucketInfo(bucketNameTest)
+               c.Assert(err, IsNil)
+               c.Assert(res.BucketInfo.Name, Equals, bucketNameTest)
+               c.Assert(res.BucketInfo.StorageClass, Equals, string(storage))
+               c.Assert(res.BucketInfo.ACL, Equals, string(ACLPublicRead))
+
+               // Delete
+               err = client.DeleteBucket(bucketNameTest)
+               c.Assert(err, IsNil)
+       }
+
+       // Error put bucket with configuration
+       err = client.CreateBucket("ERRORBUCKETNAME", StorageClass(StorageArchive))
+       c.Assert(err, NotNil)
+
+       // Create bucket with configuration and test ListBuckets
+       for _, storage := range []StorageClassType{StorageStandard, StorageIA, StorageArchive, StorageColdArchive} {
+               bucketNameTest := bucketNamePrefix + RandLowStr(6)
+               err = client.CreateBucket(bucketNameTest, StorageClass(storage))
+               c.Assert(err, IsNil)
+               time.Sleep(timeoutInOperation)
+
+               res, err := client.GetBucketInfo(bucketNameTest)
+               c.Assert(err, IsNil)
+               c.Assert(res.BucketInfo.Name, Equals, bucketNameTest)
+               c.Assert(res.BucketInfo.StorageClass, Equals, string(storage))
+
+               // Delete
+               err = client.DeleteBucket(bucketNameTest)
+               c.Assert(err, IsNil)
+       }
+}
+
+func (s *OssClientSuite) TestCreateBucketRedundancyType(c *C) {
+       bucketNameTest := bucketNamePrefix + RandLowStr(6)
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // CreateBucket creates without property
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       client.DeleteBucket(bucketNameTest)
+       time.Sleep(timeoutInOperation)
+
+       // CreateBucket creates with RedundancyZRS
+       err = client.CreateBucket(bucketNameTest, RedundancyType(RedundancyZRS))
+       c.Assert(err, IsNil)
+
+       res, err := client.GetBucketInfo(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.BucketInfo.RedundancyType, Equals, string(RedundancyZRS))
+       client.DeleteBucket(bucketNameTest)
+       time.Sleep(timeoutInOperation)
+
+       // CreateBucket creates with RedundancyLRS
+       err = client.CreateBucket(bucketNameTest, RedundancyType(RedundancyLRS))
+       c.Assert(err, IsNil)
+
+       res, err = client.GetBucketInfo(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.BucketInfo.RedundancyType, Equals, string(RedundancyLRS))
+       c.Assert(res.BucketInfo.StorageClass, Equals, string(StorageStandard))
+       client.DeleteBucket(bucketNameTest)
+       time.Sleep(timeoutInOperation)
+
+       // CreateBucket creates with ACLPublicRead RedundancyZRS
+       err = client.CreateBucket(bucketNameTest, ACL(ACLPublicRead), RedundancyType(RedundancyZRS))
+       c.Assert(err, IsNil)
+
+       res, err = client.GetBucketInfo(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.BucketInfo.RedundancyType, Equals, string(RedundancyZRS))
+       c.Assert(res.BucketInfo.ACL, Equals, string(ACLPublicRead))
+       client.DeleteBucket(bucketNameTest)
+}
+
+// TestCreateBucketNegative
+func (s *OssClientSuite) TestCreateBucketNegative(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Bucket name invalid
+       err = client.CreateBucket("xx")
+       c.Assert(err, NotNil)
+
+       err = client.CreateBucket("XXXX")
+       c.Assert(err, NotNil)
+       testLogger.Println(err)
+
+       err = client.CreateBucket("_bucket")
+       c.Assert(err, NotNil)
+       testLogger.Println(err)
+
+       // ACL invalid
+       err = client.CreateBucket(bucketNamePrefix+RandLowStr(6), ACL("InvaldAcl"))
+       c.Assert(err, NotNil)
+       testLogger.Println(err)
+}
+
+// TestDeleteBucket
+func (s *OssClientSuite) TestDeleteBucket(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Create
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // Check
+       found, err := client.IsBucketExist(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(found, Equals, true)
+
+       // Delete
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // Check
+       found, err = client.IsBucketExist(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(found, Equals, false)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, NotNil)
+}
+
+// TestDeleteBucketNegative
+func (s *OssClientSuite) TestDeleteBucketNegative(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Bucket name invalid
+       err = client.DeleteBucket("xx")
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucket("XXXX")
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucket("_bucket")
+       c.Assert(err, NotNil)
+
+       // Delete no exist bucket
+       err = client.DeleteBucket("notexist")
+       c.Assert(err, NotNil)
+
+       // No permission to delete, this ak/sk for js sdk
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       accessID := "<accessKeyId>"
+       accessKey := "<accessKeySecret>"
+       clientOtherUser, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = clientOtherUser.DeleteBucket(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestListBucket
+func (s *OssClientSuite) TestListBucket(c *C) {
+       var prefix = bucketNamePrefix + RandLowStr(6)
+       var bucketNameLbOne = prefix + "tlb1"
+       var bucketNameLbTwo = prefix + "tlb2"
+       var bucketNameLbThree = prefix + "tlb3"
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // CreateBucket
+       err = client.CreateBucket(bucketNameLbOne)
+       c.Assert(err, IsNil)
+       err = client.CreateBucket(bucketNameLbTwo)
+       c.Assert(err, IsNil)
+       err = client.CreateBucket(bucketNameLbThree)
+       c.Assert(err, IsNil)
+
+       // ListBuckets, specified prefix
+       var respHeader http.Header
+       lbr, err := client.ListBuckets(Prefix(prefix), MaxKeys(2), GetResponseHeader(&respHeader))
+       c.Assert(GetRequestId(respHeader) != "", Equals, true)
+       c.Assert(err, IsNil)
+       c.Assert(len(lbr.Buckets), Equals, 2)
+
+       // ListBuckets, specified max keys
+       lbr, err = client.ListBuckets(MaxKeys(2))
+       c.Assert(err, IsNil)
+       c.Assert(len(lbr.Buckets), Equals, 2)
+
+       // ListBuckets, specified max keys
+       lbr, err = client.ListBuckets(Marker(bucketNameLbOne), MaxKeys(1))
+       c.Assert(err, IsNil)
+       c.Assert(len(lbr.Buckets), Equals, 1)
+
+       // ListBuckets, specified max keys
+       lbr, err = client.ListBuckets(Marker(bucketNameLbOne))
+       c.Assert(err, IsNil)
+       c.Assert(len(lbr.Buckets) >= 2, Equals, true)
+
+       // DeleteBucket
+       err = client.DeleteBucket(bucketNameLbOne)
+       c.Assert(err, IsNil)
+       err = client.DeleteBucket(bucketNameLbTwo)
+       c.Assert(err, IsNil)
+       err = client.DeleteBucket(bucketNameLbThree)
+       c.Assert(err, IsNil)
+}
+
+// TestListBucket
+func (s *OssClientSuite) TestIsBucketExist(c *C) {
+       var prefix = bucketNamePrefix + RandLowStr(6)
+       var bucketNameLbOne = prefix + "tibe1"
+       var bucketNameLbTwo = prefix + "tibe11"
+       var bucketNameLbThree = prefix + "tibe111"
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // CreateBucket
+       err = client.CreateBucket(bucketNameLbOne)
+       c.Assert(err, IsNil)
+       err = client.CreateBucket(bucketNameLbTwo)
+       c.Assert(err, IsNil)
+       err = client.CreateBucket(bucketNameLbThree)
+       c.Assert(err, IsNil)
+
+       // Exist
+       exist, err := client.IsBucketExist(bucketNameLbTwo)
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, true)
+
+       exist, err = client.IsBucketExist(bucketNameLbThree)
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, true)
+
+       exist, err = client.IsBucketExist(bucketNameLbOne)
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, true)
+
+       // Not exist
+       exist, err = client.IsBucketExist(prefix + "tibe")
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, false)
+
+       exist, err = client.IsBucketExist(prefix + "tibe1111")
+       c.Assert(err, IsNil)
+       c.Assert(exist, Equals, false)
+
+       // Negative
+       exist, err = client.IsBucketExist("BucketNameInvalid")
+       c.Assert(err, NotNil)
+
+       // DeleteBucket
+       err = client.DeleteBucket(bucketNameLbOne)
+       c.Assert(err, IsNil)
+       err = client.DeleteBucket(bucketNameLbTwo)
+       c.Assert(err, IsNil)
+       err = client.DeleteBucket(bucketNameLbThree)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketAcl
+func (s *OssClientSuite) TestSetBucketAcl(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Private
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       res, err := client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+       // Set ACL_PUBLIC_R
+       err = client.SetBucketACL(bucketNameTest, ACLPublicRead)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPublicRead))
+
+       // Set ACL_PUBLIC_RW
+       err = client.SetBucketACL(bucketNameTest, ACLPublicReadWrite)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPublicReadWrite))
+
+       // Set ACL_PUBLIC_RW
+       err = client.SetBucketACL(bucketNameTest, ACLPrivate)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketAclNegative
+func (s *OssClientSuite) TestBucketAclNegative(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       err = client.SetBucketACL(bucketNameTest, "InvalidACL")
+       c.Assert(err, NotNil)
+       testLogger.Println(err)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestGetBucketAcl
+func (s *OssClientSuite) TestGetBucketAcl(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Private
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err := client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // PublicRead
+       err = client.CreateBucket(bucketNameTest, ACL(ACLPublicRead))
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPublicRead))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // PublicReadWrite
+       err = client.CreateBucket(bucketNameTest, ACL(ACLPublicReadWrite))
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPublicReadWrite))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestGetBucketAcl
+func (s *OssClientSuite) TestGetBucketLocation(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Private
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       loc, err := client.GetBucketLocation(bucketNameTest)
+       c.Assert(strings.HasPrefix(loc, "oss-"), Equals, true)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestGetBucketLocationNegative
+func (s *OssClientSuite) TestGetBucketLocationNegative(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Not exist
+       _, err = client.GetBucketLocation(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Not exist
+       _, err = client.GetBucketLocation("InvalidBucketName_")
+       c.Assert(err, NotNil)
+}
+
+// TestSetBucketLifecycle
+func (s *OssClientSuite) TestSetBucketLifecycle(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var rule1 = BuildLifecycleRuleByDate("rule1", "one", true, 2015, 11, 11)
+       var rule2 = BuildLifecycleRuleByDays("rule2", "two", true, 3)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set single rule
+       var rules = []LifecycleRule{rule1}
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+       // Double set rule
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+
+       res, err := client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(res.Rules), Equals, 1)
+       c.Assert(res.Rules[0].ID, Equals, "rule1")
+
+       err = client.DeleteBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set two rules
+       rules = []LifecycleRule{rule1, rule2}
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+
+       // Eliminate effect of cache
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(res.Rules), Equals, 2)
+       c.Assert(res.Rules[0].ID, Equals, "rule1")
+       c.Assert(res.Rules[1].ID, Equals, "rule2")
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketLifecycleNew
+func (s *OssClientSuite) TestSetBucketLifecycleNew(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       //invalid status of lifecyclerule
+       expiration := LifecycleExpiration{
+               Days: 30,
+       }
+       rule := LifecycleRule{
+               ID:         "rule1",
+               Prefix:     "one",
+               Status:     "Invalid",
+               Expiration: &expiration,
+       }
+       rules := []LifecycleRule{rule}
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, NotNil)
+
+       //invalid value of CreatedBeforeDate
+       expiration = LifecycleExpiration{
+               CreatedBeforeDate: RandStr(10),
+       }
+       rule = LifecycleRule{
+               ID:         "rule1",
+               Prefix:     "one",
+               Status:     "Enabled",
+               Expiration: &expiration,
+       }
+       rules = []LifecycleRule{rule}
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, NotNil)
+
+       //invalid value of Days
+       abortMPU := LifecycleAbortMultipartUpload{
+               Days: -30,
+       }
+       rule = LifecycleRule{
+               ID:                   "rule1",
+               Prefix:               "one",
+               Status:               "Enabled",
+               AbortMultipartUpload: &abortMPU,
+       }
+       rules = []LifecycleRule{rule}
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, NotNil)
+
+       expiration = LifecycleExpiration{
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+       }
+       rule1 := LifecycleRule{
+               ID:         "rule1",
+               Prefix:     "one",
+               Status:     "Enabled",
+               Expiration: &expiration,
+       }
+
+       abortMPU = LifecycleAbortMultipartUpload{
+               Days: 30,
+       }
+       rule2 := LifecycleRule{
+               ID:                   "rule2",
+               Prefix:               "two",
+               Status:               "Enabled",
+               Expiration:           &expiration,
+               AbortMultipartUpload: &abortMPU,
+       }
+
+       transition1 := LifecycleTransition{
+               Days:         3,
+               StorageClass: StorageIA,
+       }
+       transition2 := LifecycleTransition{
+               Days:         30,
+               StorageClass: StorageArchive,
+       }
+       transitions := []LifecycleTransition{transition1, transition2}
+       rule3 := LifecycleRule{
+               ID:                   "rule3",
+               Prefix:               "three",
+               Status:               "Enabled",
+               AbortMultipartUpload: &abortMPU,
+               Transitions:          transitions,
+       }
+
+       // Set single rule
+       rules = []LifecycleRule{rule1}
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+
+       res, err := client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(res.Rules), Equals, 1)
+       c.Assert(res.Rules[0].ID, Equals, "rule1")
+       c.Assert(res.Rules[0].Expiration, NotNil)
+       c.Assert(res.Rules[0].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z")
+
+       err = client.DeleteBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set two rule: rule1 and rule2
+       rules = []LifecycleRule{rule1, rule2}
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+
+       res, err = client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(res.Rules), Equals, 2)
+       c.Assert(res.Rules[0].ID, Equals, "rule1")
+       c.Assert(res.Rules[0].Expiration, NotNil)
+       c.Assert(res.Rules[0].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z")
+       c.Assert(res.Rules[1].ID, Equals, "rule2")
+       c.Assert(res.Rules[1].Expiration, NotNil)
+       c.Assert(res.Rules[1].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z")
+       c.Assert(res.Rules[1].AbortMultipartUpload, NotNil)
+       c.Assert(res.Rules[1].AbortMultipartUpload.Days, Equals, 30)
+
+       err = client.DeleteBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set two rule: rule2 and rule3
+       rules = []LifecycleRule{rule2, rule3}
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+
+       res, err = client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(res.Rules), Equals, 2)
+       c.Assert(res.Rules[0].ID, Equals, "rule2")
+       c.Assert(res.Rules[0].Expiration, NotNil)
+       c.Assert(res.Rules[0].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z")
+       c.Assert(res.Rules[0].AbortMultipartUpload, NotNil)
+       c.Assert(res.Rules[0].AbortMultipartUpload.Days, Equals, 30)
+       c.Assert(res.Rules[1].ID, Equals, "rule3")
+       c.Assert(res.Rules[1].AbortMultipartUpload, NotNil)
+       c.Assert(res.Rules[1].AbortMultipartUpload.Days, Equals, 30)
+       c.Assert(len(res.Rules[1].Transitions), Equals, 2)
+       c.Assert(res.Rules[1].Transitions[0].StorageClass, Equals, StorageIA)
+       c.Assert(res.Rules[1].Transitions[0].Days, Equals, 3)
+       c.Assert(res.Rules[1].Transitions[1].StorageClass, Equals, StorageArchive)
+       c.Assert(res.Rules[1].Transitions[1].Days, Equals, 30)
+
+       err = client.DeleteBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set two rule: rule1 and rule3
+       rules = []LifecycleRule{rule1, rule3}
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+
+       res, err = client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(res.Rules), Equals, 2)
+       c.Assert(res.Rules[0].ID, Equals, "rule1")
+       c.Assert(res.Rules[0].Expiration, NotNil)
+       c.Assert(res.Rules[0].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z")
+       c.Assert(res.Rules[1].ID, Equals, "rule3")
+       c.Assert(res.Rules[1].AbortMultipartUpload, NotNil)
+       c.Assert(res.Rules[1].AbortMultipartUpload.Days, Equals, 30)
+       c.Assert(len(res.Rules[1].Transitions), Equals, 2)
+       c.Assert(res.Rules[1].Transitions[0].StorageClass, Equals, StorageIA)
+       c.Assert(res.Rules[1].Transitions[0].Days, Equals, 3)
+       c.Assert(res.Rules[1].Transitions[1].StorageClass, Equals, StorageArchive)
+       c.Assert(res.Rules[1].Transitions[1].Days, Equals, 30)
+
+       err = client.DeleteBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set three rules
+       rules = []LifecycleRule{rule1, rule2, rule3}
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+
+       res, err = client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(res.Rules), Equals, 3)
+       c.Assert(res.Rules[0].ID, Equals, "rule1")
+       c.Assert(res.Rules[0].Expiration, NotNil)
+       c.Assert(res.Rules[0].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z")
+       c.Assert(res.Rules[1].ID, Equals, "rule2")
+       c.Assert(res.Rules[1].Expiration, NotNil)
+       c.Assert(res.Rules[1].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z")
+       c.Assert(res.Rules[1].AbortMultipartUpload, NotNil)
+       c.Assert(res.Rules[1].AbortMultipartUpload.Days, Equals, 30)
+       c.Assert(res.Rules[2].ID, Equals, "rule3")
+       c.Assert(res.Rules[2].AbortMultipartUpload, NotNil)
+       c.Assert(res.Rules[2].AbortMultipartUpload.Days, Equals, 30)
+       c.Assert(len(res.Rules[2].Transitions), Equals, 2)
+       c.Assert(res.Rules[2].Transitions[0].StorageClass, Equals, StorageIA)
+       c.Assert(res.Rules[2].Transitions[0].Days, Equals, 3)
+       c.Assert(res.Rules[2].Transitions[1].StorageClass, Equals, StorageArchive)
+       c.Assert(res.Rules[2].Transitions[1].Days, Equals, 30)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketLifecycleAboutVersionObject
+func (s *OssClientSuite) TestSetBucketLifecycleAboutVersionObject(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       deleteMark := true
+       expiration := LifecycleExpiration{
+               ExpiredObjectDeleteMarker: &deleteMark,
+       }
+
+       versionExpiration := LifecycleVersionExpiration{
+               NoncurrentDays: 20,
+       }
+
+       versionTransition := LifecycleVersionTransition{
+               NoncurrentDays: 10,
+               StorageClass:   "IA",
+       }
+
+       rule := LifecycleRule{
+               Status:               "Enabled",
+               Expiration:           &expiration,
+               NonVersionExpiration: &versionExpiration,
+               NonVersionTransition: &versionTransition,
+       }
+       rules := []LifecycleRule{rule}
+
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+
+       res, err := client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       c.Assert(res.Rules[0].Expiration, NotNil)
+       c.Assert(res.Rules[0].Expiration.Days, Equals, 0)
+       c.Assert(res.Rules[0].Expiration.Date, Equals, "")
+       c.Assert(*(res.Rules[0].Expiration.ExpiredObjectDeleteMarker), Equals, true)
+
+       c.Assert(res.Rules[0].NonVersionExpiration.NoncurrentDays, Equals, 20)
+       c.Assert(res.Rules[0].NonVersionTransition.NoncurrentDays, Equals, 10)
+       c.Assert(res.Rules[0].NonVersionTransition.StorageClass, Equals, StorageClassType("IA"))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketLifecycleAboutVersionObject
+func (s *OssClientSuite) TestSetBucketLifecycleAboutVersionObjectError(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       deleteMark := true
+       expiration := LifecycleExpiration{
+               ExpiredObjectDeleteMarker: &deleteMark,
+       }
+
+       versionExpiration := LifecycleVersionExpiration{
+               NoncurrentDays: 20,
+       }
+
+       versionTransition := LifecycleVersionTransition{
+               NoncurrentDays: 10,
+               StorageClass:   "IA",
+       }
+
+       // NonVersionTransition and NonVersionTransitions can not both have value
+       rule := LifecycleRule{
+               Status:                "Enabled",
+               Expiration:            &expiration,
+               NonVersionExpiration:  &versionExpiration,
+               NonVersionTransition:  &versionTransition,
+               NonVersionTransitions: []LifecycleVersionTransition{versionTransition},
+       }
+       rules := []LifecycleRule{rule}
+
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketLifecycleAboutVersionObject
+func (s *OssClientSuite) TestSetBucketLifecycleAboutVersionObjectNew(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       deleteMark := true
+       expiration := LifecycleExpiration{
+               ExpiredObjectDeleteMarker: &deleteMark,
+       }
+
+       versionExpiration := LifecycleVersionExpiration{
+               NoncurrentDays: 40,
+       }
+
+       versionTransition1 := LifecycleVersionTransition{
+               NoncurrentDays: 25,
+               StorageClass:   "IA",
+       }
+
+       versionTransition2 := LifecycleVersionTransition{
+               NoncurrentDays: 30,
+               StorageClass:   "ColdArchive",
+       }
+
+       rule := LifecycleRule{
+               Status:                "Enabled",
+               Expiration:            &expiration,
+               NonVersionExpiration:  &versionExpiration,
+               NonVersionTransitions: []LifecycleVersionTransition{versionTransition1, versionTransition2},
+       }
+       rules := []LifecycleRule{rule}
+
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+
+       res, err := client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       c.Assert(res.Rules[0].Expiration, NotNil)
+       c.Assert(res.Rules[0].Expiration.Days, Equals, 0)
+       c.Assert(res.Rules[0].Expiration.Date, Equals, "")
+       c.Assert(*(res.Rules[0].Expiration.ExpiredObjectDeleteMarker), Equals, true)
+
+       c.Assert(res.Rules[0].NonVersionExpiration.NoncurrentDays, Equals, 40)
+       c.Assert(res.Rules[0].NonVersionTransition.NoncurrentDays, Equals, 25)
+       c.Assert(res.Rules[0].NonVersionTransition.StorageClass, Equals, StorageClassType("IA"))
+       c.Assert(len(res.Rules[0].NonVersionTransitions), Equals, 2)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestDeleteBucketLifecycle
+func (s *OssClientSuite) TestDeleteBucketLifecycle(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       var rule1 = BuildLifecycleRuleByDate("rule1", "one", true, 2015, 11, 11)
+       var rule2 = BuildLifecycleRuleByDays("rule2", "two", true, 3)
+       var rules = []LifecycleRule{rule1, rule2}
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       //time.Sleep(timeoutInOperation)
+
+       err = client.DeleteBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, IsNil)
+       //time.Sleep(timeoutInOperation)
+
+       res, err := client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(res.Rules), Equals, 2)
+
+       // Delete
+       err = client.DeleteBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       //time.Sleep(timeoutInOperation)
+       res, err = client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Eliminate effect of cache
+       //time.Sleep(timeoutInOperation)
+
+       // Delete when not set
+       err = client.DeleteBucketLifecycle(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketLifecycleNegative
+func (s *OssClientSuite) TestBucketLifecycleNegative(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var rules = []LifecycleRule{}
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set with no rule
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Not exist
+       err = client.SetBucketLifecycle(bucketNameTest, rules)
+       c.Assert(err, NotNil)
+
+       // Not exist
+       _, err = client.GetBucketLifecycle(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Not exist
+       err = client.DeleteBucketLifecycle(bucketNameTest)
+       c.Assert(err, NotNil)
+}
+
+// TestSetBucketReferer
+func (s *OssClientSuite) TestSetBucketReferer(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var referers = []string{"http://www.aliyun.com", "https://www.aliyun.com"}
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err := client.GetBucketReferer(bucketNameTest)
+       c.Assert(res.AllowEmptyReferer, Equals, true)
+       c.Assert(len(res.RefererList), Equals, 0)
+
+       // Set referers
+       err = client.SetBucketReferer(bucketNameTest, referers, false)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketReferer(bucketNameTest)
+       c.Assert(res.AllowEmptyReferer, Equals, false)
+       c.Assert(len(res.RefererList), Equals, 2)
+       c.Assert(res.RefererList[0], Equals, "http://www.aliyun.com")
+       c.Assert(res.RefererList[1], Equals, "https://www.aliyun.com")
+
+       // Reset referer, referers empty
+       referers = []string{""}
+       err = client.SetBucketReferer(bucketNameTest, referers, true)
+       c.Assert(err, IsNil)
+
+       referers = []string{}
+       err = client.SetBucketReferer(bucketNameTest, referers, true)
+       c.Assert(err, IsNil)
+
+       res, err = client.GetBucketReferer(bucketNameTest)
+       c.Assert(res.AllowEmptyReferer, Equals, true)
+       c.Assert(len(res.RefererList), Equals, 0)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketRefererNegative
+func (s *OssClientSuite) TestBucketRefererNegative(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var referers = []string{""}
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Not exist
+       _, err = client.GetBucketReferer(bucketNameTest)
+       c.Assert(err, NotNil)
+       testLogger.Println(err)
+
+       // Not exist
+       err = client.SetBucketReferer(bucketNameTest, referers, true)
+       c.Assert(err, NotNil)
+       testLogger.Println(err)
+}
+
+// TestSetBucketLogging
+func (s *OssClientSuite) TestSetBucketLogging(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var bucketNameTarget = bucketNameTest + "-target"
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       err = client.CreateBucket(bucketNameTarget)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // Set logging
+       err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", true)
+       c.Assert(err, IsNil)
+       // Reset
+       err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", false)
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       res, err := client.GetBucketLogging(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.LoggingEnabled.TargetBucket, Equals, "")
+       c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "")
+
+       err = client.DeleteBucketLogging(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set to self
+       err = client.SetBucketLogging(bucketNameTest, bucketNameTest, "prefix", true)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       err = client.DeleteBucket(bucketNameTarget)
+       c.Assert(err, IsNil)
+}
+
+// TestDeleteBucketLogging
+func (s *OssClientSuite) TestDeleteBucketLogging(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var bucketNameTarget = bucketNameTest + "-target"
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       err = client.CreateBucket(bucketNameTarget)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // Get when not set
+       res, err := client.GetBucketLogging(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.LoggingEnabled.TargetBucket, Equals, "")
+       c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "")
+
+       // Set
+       err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", true)
+       c.Assert(err, IsNil)
+
+       // Get
+       time.Sleep(timeoutInOperation)
+       res, err = client.GetBucketLogging(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.LoggingEnabled.TargetBucket, Equals, bucketNameTarget)
+       c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "prefix")
+
+       // Set
+       err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", false)
+       c.Assert(err, IsNil)
+
+       // Get
+       time.Sleep(timeoutInOperation)
+       res, err = client.GetBucketLogging(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.LoggingEnabled.TargetBucket, Equals, "")
+       c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "")
+
+       // Delete
+       err = client.DeleteBucketLogging(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Get after delete
+       time.Sleep(timeoutInOperation)
+       res, err = client.GetBucketLogging(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.LoggingEnabled.TargetBucket, Equals, "")
+       c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "")
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       err = client.DeleteBucket(bucketNameTarget)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketLoggingNegative
+func (s *OssClientSuite) TestSetBucketLoggingNegative(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var bucketNameTarget = bucketNameTest + "-target"
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Not exist
+       _, err = client.GetBucketLogging(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Not exist
+       err = client.SetBucketLogging(bucketNameTest, "targetbucket", "prefix", true)
+       c.Assert(err, NotNil)
+
+       // Not exist
+       err = client.DeleteBucketLogging(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // Target bucket not exist
+       err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", true)
+       c.Assert(err, NotNil)
+
+       // Parameter invalid
+       err = client.SetBucketLogging(bucketNameTest, "XXXX", "prefix", true)
+       c.Assert(err, NotNil)
+
+       err = client.SetBucketLogging(bucketNameTest, "xx", "prefix", true)
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketWebsite
+func (s *OssClientSuite) TestSetBucketWebsite(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var indexWebsite = "myindex.html"
+       var errorWebsite = "myerror.html"
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set
+       err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite)
+       c.Assert(err, IsNil)
+
+       // Double set
+       err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite)
+       c.Assert(err, IsNil)
+
+       res, err := client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite)
+       c.Assert(res.ErrorDocument.Key, Equals, errorWebsite)
+
+       // Reset
+       err = client.SetBucketWebsite(bucketNameTest, "your"+indexWebsite, "your"+errorWebsite)
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       res, err = client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.IndexDocument.Suffix, Equals, "your"+indexWebsite)
+       c.Assert(res.ErrorDocument.Key, Equals, "your"+errorWebsite)
+
+       err = client.DeleteBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set after delete
+       err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite)
+       c.Assert(err, IsNil)
+
+       // Eliminate effect of cache
+       time.Sleep(timeoutInOperation)
+
+       res, err = client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite)
+       c.Assert(res.ErrorDocument.Key, Equals, errorWebsite)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestDeleteBucketWebsite
+func (s *OssClientSuite) TestDeleteBucketWebsite(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var indexWebsite = "myindex.html"
+       var errorWebsite = "myerror.html"
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // Get
+       res, err := client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Detele without set
+       err = client.DeleteBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set
+       err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite)
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       res, err = client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite)
+       c.Assert(res.ErrorDocument.Key, Equals, errorWebsite)
+
+       // Detele
+       time.Sleep(timeoutInOperation)
+       err = client.DeleteBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       res, err = client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Detele after delete
+       err = client.DeleteBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketWebsiteNegative
+func (s *OssClientSuite) TestSetBucketWebsiteNegative(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var indexWebsite = "myindex.html"
+       var errorWebsite = "myerror.html"
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+
+       // Not exist
+       _, err = client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucketWebsite(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite)
+       c.Assert(err, NotNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set
+       time.Sleep(timeoutInOperation)
+       err = client.SetBucketWebsite(bucketNameTest, "myindex", "myerror")
+       c.Assert(err, IsNil)
+
+       res, err := client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.IndexDocument.Suffix, Equals, "myindex")
+       c.Assert(res.ErrorDocument.Key, Equals, "myerror")
+
+       // Detele
+       err = client.DeleteBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       _, err = client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Detele after delete
+       err = client.DeleteBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketWebsiteDetail
+func (s *OssClientSuite) TestSetBucketWebsiteDetail(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var indexWebsite = "myindex.html"
+       var errorWebsite = "myerror.html"
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       btrue := true
+       bfalse := false
+       // Define one routing rule
+       ruleOk := RoutingRule{
+               RuleNumber: 1,
+               Condition: Condition{
+                       KeyPrefixEquals:             "",
+                       HTTPErrorCodeReturnedEquals: 404,
+               },
+               Redirect: Redirect{
+                       RedirectType: "Mirror",
+                       // PassQueryString: &btrue,             // set default value
+                       MirrorURL: "http://www.test.com/",
+                       // MirrorPassQueryString:&btrue,        // set default value
+                       // MirrorFollowRedirect:&bfalse,        // set default value
+                       // MirrorCheckMd5:&bfalse,                      // set default value
+                       MirrorHeaders: MirrorHeaders{
+                               // PassAll:&bfalse,                     // set default value
+                               Pass:   []string{"myheader-key1", "myheader-key2"},
+                               Remove: []string{"myheader-key3", "myheader-key4"},
+                               Set: []MirrorHeaderSet{
+                                       MirrorHeaderSet{
+                                               Key:   "myheader-key5",
+                                               Value: "myheader-value5",
+                                       },
+                               },
+                       },
+               },
+       }
+
+       // Define array routing rule
+       ruleArrOk := []RoutingRule{
+               RoutingRule{
+                       RuleNumber: 2,
+                       Condition: Condition{
+                               KeyPrefixEquals:             "abc/",
+                               HTTPErrorCodeReturnedEquals: 404,
+                               IncludeHeader: []IncludeHeader{
+                                       IncludeHeader{
+                                               Key:    "host",
+                                               Equals: "test.oss-cn-beijing-internal.aliyuncs.com",
+                                       },
+                               },
+                       },
+                       Redirect: Redirect{
+                               RedirectType:     "AliCDN",
+                               Protocol:         "http",
+                               HostName:         "www.test.com",
+                               PassQueryString:  &bfalse,
+                               ReplaceKeyWith:   "prefix/${key}.suffix",
+                               HttpRedirectCode: 301,
+                       },
+               },
+               RoutingRule{
+                       RuleNumber: 3,
+                       Condition: Condition{
+                               KeyPrefixEquals:             "",
+                               HTTPErrorCodeReturnedEquals: 404,
+                       },
+                       Redirect: Redirect{
+                               RedirectType:          "Mirror",
+                               PassQueryString:       &btrue,
+                               MirrorURL:             "http://www.test.com/",
+                               MirrorPassQueryString: &btrue,
+                               MirrorFollowRedirect:  &bfalse,
+                               MirrorCheckMd5:        &bfalse,
+                               MirrorHeaders: MirrorHeaders{
+                                       PassAll: &btrue,
+                                       Pass:    []string{"myheader-key1", "myheader-key2"},
+                                       Remove:  []string{"myheader-key3", "myheader-key4"},
+                                       Set: []MirrorHeaderSet{
+                                               MirrorHeaderSet{
+                                                       Key:   "myheader-key5",
+                                                       Value: "myheader-value5",
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+
+       // Set one routing rule
+       wxmlOne := WebsiteXML{}
+       wxmlOne.RoutingRules = append(wxmlOne.RoutingRules, ruleOk)
+       var responseHeader http.Header
+       err = client.SetBucketWebsiteDetail(bucketNameTest, wxmlOne, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       res, err := client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.RoutingRules[0].Redirect.RedirectType, Equals, "Mirror")
+       c.Assert(*res.RoutingRules[0].Redirect.PassQueryString, Equals, false)
+       c.Assert(*res.RoutingRules[0].Redirect.MirrorPassQueryString, Equals, false)
+       c.Assert(*res.RoutingRules[0].Redirect.MirrorFollowRedirect, Equals, true)
+       c.Assert(*res.RoutingRules[0].Redirect.MirrorCheckMd5, Equals, false)
+       c.Assert(*res.RoutingRules[0].Redirect.MirrorHeaders.PassAll, Equals, false)
+
+       // Set one routing rule and IndexDocument, IndexDocument
+       wxml := WebsiteXML{
+               IndexDocument: IndexDocument{Suffix: indexWebsite},
+               ErrorDocument: ErrorDocument{Key: errorWebsite},
+       }
+       wxml.RoutingRules = append(wxml.RoutingRules, ruleOk)
+       err = client.SetBucketWebsiteDetail(bucketNameTest, wxml, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       res, err = client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite)
+       c.Assert(res.ErrorDocument.Key, Equals, errorWebsite)
+       c.Assert(res.RoutingRules[0].Redirect.RedirectType, Equals, "Mirror")
+
+       // Set array routing rule
+       wxml.RoutingRules = append(wxml.RoutingRules, ruleArrOk...)
+       err = client.SetBucketWebsiteDetail(bucketNameTest, wxml, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       res, err = client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite)
+       c.Assert(res.ErrorDocument.Key, Equals, errorWebsite)
+       c.Assert(len(res.RoutingRules), Equals, 3)
+       c.Assert(res.RoutingRules[1].Redirect.RedirectType, Equals, "AliCDN")
+       c.Assert(*res.RoutingRules[2].Redirect.MirrorPassQueryString, Equals, true)
+       c.Assert(*res.RoutingRules[2].Redirect.MirrorFollowRedirect, Equals, false)
+
+       // Define one error routing rule
+       ruleErr := RoutingRule{
+               RuleNumber: 1,
+               Redirect: Redirect{
+                       RedirectType:    "Mirror",
+                       PassQueryString: &btrue,
+               },
+       }
+       // Define array error routing rule
+       rulesErrArr := []RoutingRule{
+               RoutingRule{
+                       RuleNumber: 1,
+                       Redirect: Redirect{
+                               RedirectType:    "Mirror",
+                               PassQueryString: &btrue,
+                       },
+               },
+               RoutingRule{
+                       RuleNumber: 2,
+                       Redirect: Redirect{
+                               RedirectType:    "Mirror",
+                               PassQueryString: &btrue,
+                       },
+               },
+       }
+
+       ruleIntErr := RoutingRule{
+               // RuleNumber:0,                                                // set NULL value
+               Condition: Condition{
+                       KeyPrefixEquals:             "",
+                       HTTPErrorCodeReturnedEquals: 404,
+               },
+               Redirect: Redirect{
+                       RedirectType: "Mirror",
+                       // PassQueryString: &btrue,             // set default value
+                       MirrorURL: "http://www.test.com/",
+                       // MirrorPassQueryString:&btrue,        // set default value
+                       // MirrorFollowRedirect:&bfalse,        // set default value
+                       // MirrorCheckMd5:&bfalse,                      // set default value
+                       MirrorHeaders: MirrorHeaders{
+                               // PassAll:&bfalse,                     // set default value
+                               Pass:   []string{"myheader-key1", "myheader-key2"},
+                               Remove: []string{"myheader-key3", "myheader-key4"},
+                               Set: []MirrorHeaderSet{
+                                       MirrorHeaderSet{
+                                               Key:   "myheader-key5",
+                                               Value: "myheader-value5",
+                                       },
+                               },
+                       },
+               },
+       }
+
+       // Set one int type error rule
+       wxmlIntErr := WebsiteXML{}
+       wxmlIntErr.RoutingRules = append(wxmlIntErr.RoutingRules, ruleIntErr)
+       err = client.SetBucketWebsiteDetail(bucketNameTest, wxmlIntErr)
+       c.Assert(err, NotNil)
+
+       // Set one error rule
+       wxmlErr := WebsiteXML{}
+       wxmlErr.RoutingRules = append(wxmlErr.RoutingRules, ruleErr)
+       err = client.SetBucketWebsiteDetail(bucketNameTest, wxmlErr)
+       c.Assert(err, NotNil)
+
+       // Set one error rule and one correct rule
+       wxmlErr.RoutingRules = append(wxmlErr.RoutingRules, ruleOk)
+       err = client.SetBucketWebsiteDetail(bucketNameTest, wxmlErr)
+       c.Assert(err, NotNil)
+
+       wxmlErrRuleArr := WebsiteXML{}
+       wxmlErrRuleArr.RoutingRules = append(wxmlErrRuleArr.RoutingRules, rulesErrArr...)
+       // Set array error routing rule
+       err = client.SetBucketWebsiteDetail(bucketNameTest, wxmlErrRuleArr)
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketWebsiteXml
+func (s *OssClientSuite) TestSetBucketWebsiteXml(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // Define one routing rule
+       ruleOk := RoutingRule{
+               RuleNumber: 1,
+               Condition: Condition{
+                       KeyPrefixEquals:             "",
+                       HTTPErrorCodeReturnedEquals: 404,
+               },
+               Redirect: Redirect{
+                       RedirectType: "Mirror",
+                       // PassQueryString: &btrue,             // set default value
+                       MirrorURL: "http://www.test.com/",
+                       // MirrorPassQueryString:&btrue,        // set default value
+                       // MirrorFollowRedirect:&bfalse,        // set default value
+                       // MirrorCheckMd5:&bfalse,                      // set default value
+                       MirrorHeaders: MirrorHeaders{
+                               // PassAll:&bfalse,                     // set default value
+                               Pass:   []string{"myheader-key1", "myheader-key2"},
+                               Remove: []string{"myheader-key3", "myheader-key4"},
+                               Set: []MirrorHeaderSet{
+                                       MirrorHeaderSet{
+                                               Key:   "myheader-key5",
+                                               Value: "myheader-value5",
+                                       },
+                               },
+                       },
+               },
+       }
+
+       // Set one routing rule
+       wxmlOne := WebsiteXML{}
+       wxmlOne.RoutingRules = append(wxmlOne.RoutingRules, ruleOk)
+       bs, err := xml.Marshal(wxmlOne)
+       c.Assert(err, IsNil)
+
+       var responseHeader http.Header
+       err = client.SetBucketWebsiteXml(bucketNameTest, string(bs), GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       res, err := client.GetBucketWebsite(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.RoutingRules[0].Redirect.RedirectType, Equals, "Mirror")
+       c.Assert(*res.RoutingRules[0].Redirect.PassQueryString, Equals, false)
+       c.Assert(*res.RoutingRules[0].Redirect.MirrorPassQueryString, Equals, false)
+       c.Assert(*res.RoutingRules[0].Redirect.MirrorFollowRedirect, Equals, true)
+       c.Assert(*res.RoutingRules[0].Redirect.MirrorCheckMd5, Equals, false)
+       c.Assert(*res.RoutingRules[0].Redirect.MirrorHeaders.PassAll, Equals, false)
+}
+
+// TestSetBucketCORS
+func (s *OssClientSuite) TestSetBucketCORS(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var rule1 = CORSRule{
+               AllowedOrigin: []string{"*"},
+               AllowedMethod: []string{"PUT", "GET", "POST"},
+               AllowedHeader: []string{},
+               ExposeHeader:  []string{},
+               MaxAgeSeconds: 100,
+       }
+
+       var rule2 = CORSRule{
+               AllowedOrigin: []string{"http://www.a.com", "http://www.b.com"},
+               AllowedMethod: []string{"GET"},
+               AllowedHeader: []string{"Authorization"},
+               ExposeHeader:  []string{"x-oss-test", "x-oss-test1"},
+               MaxAgeSeconds: 200,
+       }
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // Set
+       err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1})
+       c.Assert(err, IsNil)
+
+       gbcr, err := client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(gbcr.CORSRules), Equals, 1)
+       c.Assert(len(gbcr.CORSRules[0].AllowedOrigin), Equals, 1)
+       c.Assert(len(gbcr.CORSRules[0].AllowedMethod), Equals, 3)
+       c.Assert(len(gbcr.CORSRules[0].AllowedHeader), Equals, 0)
+       c.Assert(len(gbcr.CORSRules[0].ExposeHeader), Equals, 0)
+       c.Assert(gbcr.CORSRules[0].MaxAgeSeconds, Equals, 100)
+
+       // Double set
+       err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1})
+       c.Assert(err, IsNil)
+
+       gbcr, err = client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(gbcr.CORSRules), Equals, 1)
+       c.Assert(len(gbcr.CORSRules[0].AllowedOrigin), Equals, 1)
+       c.Assert(len(gbcr.CORSRules[0].AllowedMethod), Equals, 3)
+       c.Assert(len(gbcr.CORSRules[0].AllowedHeader), Equals, 0)
+       c.Assert(len(gbcr.CORSRules[0].ExposeHeader), Equals, 0)
+       c.Assert(gbcr.CORSRules[0].MaxAgeSeconds, Equals, 100)
+
+       // Set rule2
+       err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule2})
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       gbcr, err = client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(gbcr.CORSRules), Equals, 1)
+       c.Assert(len(gbcr.CORSRules[0].AllowedOrigin), Equals, 2)
+       c.Assert(len(gbcr.CORSRules[0].AllowedMethod), Equals, 1)
+       c.Assert(len(gbcr.CORSRules[0].AllowedHeader), Equals, 1)
+       c.Assert(len(gbcr.CORSRules[0].ExposeHeader), Equals, 2)
+       c.Assert(gbcr.CORSRules[0].MaxAgeSeconds, Equals, 200)
+
+       // Reset
+       err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1, rule2})
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       gbcr, err = client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(gbcr.CORSRules), Equals, 2)
+
+       // Set after delete
+       err = client.DeleteBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1, rule2})
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       gbcr, err = client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(len(gbcr.CORSRules), Equals, 2)
+
+       err = client.DeleteBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestDeleteBucketCORS
+func (s *OssClientSuite) TestDeleteBucketCORS(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var rule = CORSRule{
+               AllowedOrigin: []string{"*"},
+               AllowedMethod: []string{"PUT", "GET", "POST"},
+               AllowedHeader: []string{},
+               ExposeHeader:  []string{},
+               MaxAgeSeconds: 100,
+       }
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // Delete not set
+       err = client.DeleteBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Set
+       err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule})
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       _, err = client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Detele
+       err = client.DeleteBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       _, err = client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Detele after deleting
+       err = client.DeleteBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestSetBucketCORSNegative
+func (s *OssClientSuite) TestSetBucketCORSNegative(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var rule = CORSRule{
+               AllowedOrigin: []string{"*"},
+               AllowedMethod: []string{"PUT", "GET", "POST"},
+               AllowedHeader: []string{},
+               ExposeHeader:  []string{},
+               MaxAgeSeconds: 100,
+       }
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+
+       // Not exist
+       _, err = client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucketCORS(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule})
+       c.Assert(err, NotNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       _, err = client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Set
+       err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule})
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       _, err = client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Delete
+       err = client.DeleteBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       _, err = client.GetBucketCORS(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Delete after deleting
+       err = client.DeleteBucketCORS(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestGetBucketInfo
+func (s *OssClientSuite) TestGetBucketInfo(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       res, err := client.GetBucketInfo(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.BucketInfo.Name, Equals, bucketNameTest)
+       c.Assert(strings.HasPrefix(res.BucketInfo.Location, "oss-"), Equals, true)
+       c.Assert(res.BucketInfo.ACL, Equals, "private")
+       c.Assert(strings.HasSuffix(res.BucketInfo.ExtranetEndpoint, ".com"), Equals, true)
+       c.Assert(strings.HasSuffix(res.BucketInfo.IntranetEndpoint, ".com"), Equals, true)
+       c.Assert(res.BucketInfo.CreationDate, NotNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestGetBucketInfoNegative
+func (s *OssClientSuite) TestGetBucketInfoNegative(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Not exist
+       _, err = client.GetBucketInfo(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       // Bucket name invalid
+       _, err = client.GetBucketInfo("InvalidBucketName_")
+       c.Assert(err, NotNil)
+}
+
+// TestEndpointFormat
+func (s *OssClientSuite) TestEndpointFormat(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       // http://host
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       res, err := client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       time.Sleep(timeoutInOperation)
+
+       // http://host:port
+       client, err = New(endpoint+":80", accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       time.Sleep(timeoutInOperation)
+       res, err = client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestCname
+func (s *OssClientSuite) _TestCname(c *C) {
+       var bucketNameTest = "<my-bucket-cname>"
+
+       client, err := New("<endpoint>", "<accessKeyId>", "<accessKeySecret>", UseCname(true))
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       _, err = client.ListBuckets()
+       c.Assert(err, NotNil)
+
+       res, err := client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPrivate))
+}
+
+// TestCnameNegative
+func (s *OssClientSuite) _TestCnameNegative(c *C) {
+       var bucketNameTest = "<my-bucket-cname>"
+
+       client, err := New("<endpoint>", "<accessKeyId>", "<accessKeySecret>", UseCname(true))
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       _, err = client.ListBuckets()
+       c.Assert(err, NotNil)
+
+       _, err = client.GetBucketACL(bucketNameTest)
+       c.Assert(err, NotNil)
+}
+
+// _TestHTTPS
+func (s *OssClientSuite) _TestHTTPS(c *C) {
+       var bucketNameTest = "<my-bucket-https>"
+
+       client, err := New("<endpoint>", "<accessKeyId>", "<accessKeySecret>")
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       res, err := client.GetBucketACL(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestClientOption
+func (s *OssClientSuite) TestClientOption(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       client, err := New(endpoint, accessID, accessKey, UseCname(true),
+               Timeout(11, 12), SecurityToken("token"), Proxy(proxyHost))
+       c.Assert(err, IsNil)
+
+       // CreateBucket timeout
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       c.Assert(client.Conn.config.HTTPTimeout.ConnectTimeout, Equals, time.Second*11)
+       c.Assert(client.Conn.config.HTTPTimeout.ReadWriteTimeout, Equals, time.Second*12)
+       c.Assert(client.Conn.config.HTTPTimeout.HeaderTimeout, Equals, time.Second*12)
+       c.Assert(client.Conn.config.HTTPTimeout.IdleConnTimeout, Equals, time.Second*12)
+       c.Assert(client.Conn.config.HTTPTimeout.LongTimeout, Equals, time.Second*12*10)
+
+       c.Assert(client.Conn.config.SecurityToken, Equals, "token")
+       c.Assert(client.Conn.config.IsCname, Equals, true)
+
+       c.Assert(client.Conn.config.IsUseProxy, Equals, true)
+       c.Assert(client.Config.ProxyHost, Equals, proxyHost)
+
+       client, err = New(endpoint, accessID, accessKey, AuthProxy(proxyHost, proxyUser, proxyPasswd))
+
+       c.Assert(client.Conn.config.IsUseProxy, Equals, true)
+       c.Assert(client.Config.ProxyHost, Equals, proxyHost)
+       c.Assert(client.Conn.config.IsAuthProxy, Equals, true)
+       c.Assert(client.Conn.config.ProxyUser, Equals, proxyUser)
+       c.Assert(client.Conn.config.ProxyPassword, Equals, proxyPasswd)
+
+       client, err = New(endpoint, accessID, accessKey, UserAgent("go sdk user agent"))
+       c.Assert(client.Conn.config.UserAgent, Equals, "go sdk user agent")
+
+       // Check we can overide the http.Client
+       httpClient := new(http.Client)
+       client, err = New(endpoint, accessID, accessKey, HTTPClient(httpClient))
+       c.Assert(client.HTTPClient, Equals, httpClient)
+       c.Assert(client.Conn.client, Equals, httpClient)
+       client, err = New(endpoint, accessID, accessKey)
+       c.Assert(client.HTTPClient, IsNil)
+}
+
+// TestProxy
+func (s *OssClientSuite) ProxyTestFunc(c *C, authVersion AuthVersionType, extraHeaders []string) {
+       bucketNameTest := bucketNamePrefix + RandLowStr(6)
+       objectName := "体育/奥运/首金"
+       objectValue := "大江东去,浪淘尽,千古风流人物。 故垒西边,人道是、三国周郎赤壁。"
+
+       client, err := New(endpoint, accessID, accessKey, AuthProxy(proxyHost, proxyUser, proxyPasswd))
+
+       oldType := client.Config.AuthVersion
+       oldHeaders := client.Config.AdditionalHeaders
+       client.Config.AuthVersion = authVersion
+       client.Config.AdditionalHeaders = extraHeaders
+
+       // Create bucket
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // Get bucket info
+       _, err = client.GetBucketInfo(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketNameTest)
+
+       // Sign URL
+       str, err := bucket.SignURL(objectName, HTTPPut, 60)
+       c.Assert(err, IsNil)
+       if bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Put object with URL
+       err = bucket.PutObjectWithURL(str, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Sign URL for get object
+       str, err = bucket.SignURL(objectName, HTTPGet, 60)
+       c.Assert(err, IsNil)
+       if bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Get object with URL
+       body, err := bucket.GetObjectWithURL(str)
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // Put object
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Get object
+       _, err = bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+
+       // List objects
+       _, err = bucket.ListObjects()
+       c.Assert(err, IsNil)
+
+       // Delete object
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       client.Config.AuthVersion = oldType
+       client.Config.AdditionalHeaders = oldHeaders
+}
+
+func (s *OssClientSuite) TestProxy(c *C) {
+       s.ProxyTestFunc(c, AuthV1, []string{})
+       s.ProxyTestFunc(c, AuthV2, []string{})
+       s.ProxyTestFunc(c, AuthV2, []string{"host", "range", "user-agent"})
+}
+
+// TestProxy for https endpoint
+func (s *OssClientSuite) TestHttpsEndpointProxy(c *C) {
+       bucketNameTest := bucketNamePrefix + RandLowStr(6)
+       objectName := objectNamePrefix + RandLowStr(6)
+       objectValue := RandLowStr(100)
+
+       httpsEndPoint := ""
+       if strings.HasPrefix(endpoint, "http://") {
+               httpsEndPoint = strings.Replace(endpoint, "http://", "https://", 1)
+       } else if !strings.HasPrefix(endpoint, "https://") {
+               httpsEndPoint = "https://" + endpoint
+       } else {
+               httpsEndPoint = endpoint
+       }
+
+       client, err := New(httpsEndPoint, accessID, accessKey, AuthProxy(proxyHost, proxyUser, proxyPasswd))
+
+       // Create bucket
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketNameTest)
+
+       // Put object
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Get object
+       _, err = bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+
+       // List objects
+       _, err = bucket.ListObjects()
+       c.Assert(err, IsNil)
+
+       // Delete object
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// Private
+func (s *OssClientSuite) checkBucket(buckets []BucketProperties, bucket string) bool {
+       for _, v := range buckets {
+               if v.Name == bucket {
+                       return true
+               }
+       }
+       return false
+}
+
+func (s *OssClientSuite) getBucket(buckets []BucketProperties, bucket string) (bool, BucketProperties) {
+       for _, v := range buckets {
+               if v.Name == bucket {
+                       return true, v
+               }
+       }
+       return false, BucketProperties{}
+}
+
+func (s *OssClientSuite) TestHttpLogNotSignUrl(c *C) {
+       logName := "." + string(os.PathSeparator) + "test-go-sdk-httpdebug.log" + RandStr(5)
+       f, err := os.OpenFile(logName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
+       c.Assert(err, IsNil)
+
+       client, err := New(endpoint, accessID, accessKey)
+       client.Config.LogLevel = Debug
+
+       client.Config.Logger = log.New(f, "", log.LstdFlags)
+
+       var testBucketName = bucketNamePrefix + RandLowStr(6)
+
+       // CreateBucket
+       err = client.CreateBucket(testBucketName)
+       f.Close()
+
+       // read log file,get http info
+       contents, err := ioutil.ReadFile(logName)
+       c.Assert(err, IsNil)
+
+       httpContent := string(contents)
+       //fmt.Println(httpContent)
+
+       c.Assert(strings.Contains(httpContent, "signStr"), Equals, true)
+       c.Assert(strings.Contains(httpContent, "Method:"), Equals, true)
+
+       // delete test bucket and log
+       os.Remove(logName)
+       client.DeleteBucket(testBucketName)
+}
+
+func (s *OssClientSuite) HttpLogSignUrlTestFunc(c *C, authVersion AuthVersionType, extraHeaders []string) {
+       logName := "." + string(os.PathSeparator) + "test-go-sdk-httpdebug-signurl.log" + RandStr(5)
+       f, err := os.OpenFile(logName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
+       c.Assert(err, IsNil)
+
+       client, err := New(endpoint, accessID, accessKey)
+       client.Config.LogLevel = Debug
+       client.Config.Logger = log.New(f, "", log.LstdFlags)
+
+       oldType := client.Config.AuthVersion
+       oldHeaders := client.Config.AdditionalHeaders
+       client.Config.AuthVersion = authVersion
+       client.Config.AdditionalHeaders = extraHeaders
+
+       var testBucketName = bucketNamePrefix + RandLowStr(6)
+
+       // CreateBucket
+       err = client.CreateBucket(testBucketName)
+       f.Close()
+
+       // clear log
+       f, err = os.OpenFile(logName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
+       client.Config.Logger = log.New(f, "", log.LstdFlags)
+
+       bucket, _ := client.Bucket(testBucketName)
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(20)
+
+       // Sign URL for put
+       str, err := bucket.SignURL(objectName, HTTPPut, 60)
+       c.Assert(err, IsNil)
+       if bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Error put object with URL
+       err = bucket.PutObjectWithURL(str, strings.NewReader(objectValue), ContentType("image/tiff"))
+       f.Close()
+
+       // read log file,get http info
+       contents, err := ioutil.ReadFile(logName)
+       c.Assert(err, IsNil)
+
+       httpContent := string(contents)
+       //fmt.Println(httpContent)
+
+       c.Assert(strings.Contains(httpContent, "signStr"), Equals, true)
+       c.Assert(strings.Contains(httpContent, "Method:"), Equals, true)
+
+       // delete test bucket and log
+       os.Remove(logName)
+       client.DeleteBucket(testBucketName)
+
+       client.Config.AuthVersion = oldType
+       client.Config.AdditionalHeaders = oldHeaders
+}
+
+func (s *OssClientSuite) TestHttpLogSignUrl(c *C) {
+       s.HttpLogSignUrlTestFunc(c, AuthV1, []string{})
+       s.HttpLogSignUrlTestFunc(c, AuthV2, []string{})
+       s.HttpLogSignUrlTestFunc(c, AuthV2, []string{"host", "range", "user-agent"})
+}
+
+func (s *OssClientSuite) TestSetLimitUploadSpeed(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.LimitUploadSpeed(100)
+
+       goVersion := runtime.Version()
+       pSlice := strings.Split(strings.ToLower(goVersion), ".")
+
+       // compare with go1.7
+       if len(pSlice) >= 2 {
+               if pSlice[0] > "go1" {
+                       c.Assert(err, IsNil)
+               } else if pSlice[0] == "go1" {
+                       subVersion, _ := strconv.Atoi(pSlice[1])
+                       if subVersion >= 7 {
+                               c.Assert(err, IsNil)
+                       } else {
+                               c.Assert(err, NotNil)
+                       }
+               } else {
+                       c.Assert(err, NotNil)
+               }
+       }
+}
+
+func (s *OssClientSuite) TestBucketEncyptionError(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // SetBucketEncryption:AES256 ,"123"
+       encryptionRule := ServerEncryptionRule{}
+       encryptionRule.SSEDefault.SSEAlgorithm = string(AESAlgorithm)
+       encryptionRule.SSEDefault.KMSMasterKeyID = "123"
+
+       var responseHeader http.Header
+       err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader))
+       c.Assert(err, NotNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // GetBucketEncryption
+       _, err = client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, NotNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // Get default bucket info
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "")
+       c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "")
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, "")
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestBucketEncryptionPutAndGetAndDelete(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // SetBucketEncryption:KMS ,""
+       encryptionRule := ServerEncryptionRule{}
+       encryptionRule.SSEDefault.SSEAlgorithm = string(KMSAlgorithm)
+
+       var responseHeader http.Header
+       err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // GetBucketEncryption
+       getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // check encryption value
+       c.Assert(encryptionRule.SSEDefault.SSEAlgorithm, Equals, getResult.SSEDefault.SSEAlgorithm)
+       c.Assert(encryptionRule.SSEDefault.KMSMasterKeyID, Equals, getResult.SSEDefault.KMSMasterKeyID)
+
+       // delete bucket encyption
+       err = client.DeleteBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // GetBucketEncryption failure
+       _, err = client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, NotNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // Get default bucket info
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "")
+       c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "")
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, "")
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestBucketEncryptionWithSm4(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // SetBucketEncryption:SM4 ,""
+       encryptionRule := ServerEncryptionRule{}
+       encryptionRule.SSEDefault.SSEAlgorithm = string(SM4Algorithm)
+
+       var responseHeader http.Header
+       err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // GetBucketEncryption
+       getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // check encryption value
+       c.Assert(getResult.SSEDefault.SSEAlgorithm, Equals, string(SM4Algorithm))
+       c.Assert(getResult.SSEDefault.KMSMasterKeyID, Equals, "")
+       c.Assert(getResult.SSEDefault.KMSDataEncryption, Equals, "")
+
+       // Get default bucket info
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, string(SM4Algorithm))
+       c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "")
+       c.Assert(bucketResult.BucketInfo.SseRule.KMSDataEncryption, Equals, "")
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestBucketEncryptionWithKmsSm4(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // SetBucketEncryption:SM4 ,""
+       encryptionRule := ServerEncryptionRule{}
+       encryptionRule.SSEDefault.SSEAlgorithm = string(KMSAlgorithm)
+       encryptionRule.SSEDefault.KMSDataEncryption = string(SM4Algorithm)
+
+       var responseHeader http.Header
+       err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // GetBucketEncryption
+       getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // check encryption value
+       c.Assert(getResult.SSEDefault.SSEAlgorithm, Equals, string(KMSAlgorithm))
+       c.Assert(getResult.SSEDefault.KMSMasterKeyID, Equals, "")
+       c.Assert(getResult.SSEDefault.KMSDataEncryption, Equals, string(SM4Algorithm))
+
+       // Get default bucket info
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, string(KMSAlgorithm))
+       c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "")
+       c.Assert(bucketResult.BucketInfo.SseRule.KMSDataEncryption, Equals, string(SM4Algorithm))
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestBucketEncyptionPutObjectSuccess(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // SetBucketEncryption:KMS ,""
+       encryptionRule := ServerEncryptionRule{}
+       encryptionRule.SSEDefault.SSEAlgorithm = string(KMSAlgorithm)
+
+       var responseHeader http.Header
+       err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // GetBucketEncryption
+       getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // check encryption value
+       c.Assert(encryptionRule.SSEDefault.SSEAlgorithm, Equals, getResult.SSEDefault.SSEAlgorithm)
+       c.Assert(encryptionRule.SSEDefault.KMSMasterKeyID, Equals, getResult.SSEDefault.KMSMasterKeyID)
+
+       // Get default bucket info
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "KMS")
+       c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "")
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, "")
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestBucketEncyptionPutObjectError(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // SetBucketEncryption:KMS ,""
+       encryptionRule := ServerEncryptionRule{}
+       encryptionRule.SSEDefault.SSEAlgorithm = string(KMSAlgorithm)
+       kmsId := "123"
+       encryptionRule.SSEDefault.KMSMasterKeyID = kmsId
+
+       var responseHeader http.Header
+       err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // GetBucketEncryption
+       getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // check encryption value
+       c.Assert(encryptionRule.SSEDefault.SSEAlgorithm, Equals, getResult.SSEDefault.SSEAlgorithm)
+       c.Assert(encryptionRule.SSEDefault.KMSMasterKeyID, Equals, getResult.SSEDefault.KMSMasterKeyID)
+
+       // Get default bucket info
+       bucketResult, err := client.GetBucketInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "KMS")
+       c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, kmsId)
+       c.Assert(bucketResult.BucketInfo.Versioning, Equals, "")
+
+       // put and get object failure
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // put object failure
+       objectName := objectNamePrefix + RandStr(8)
+       context := RandStr(100)
+       err = bucket.PutObject(objectName, strings.NewReader(context))
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestBucketTaggingOperation(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       var respHeader http.Header
+
+       // Bucket Tagging
+       var tagging Tagging
+       tagging.Tags = []Tag{Tag{Key: "testkey2", Value: "testvalue2"}}
+       err = client.SetBucketTagging(bucketName, tagging, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       c.Assert(GetRequestId(respHeader) != "", Equals, true)
+
+       getResult, err := client.GetBucketTagging(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(getResult.Tags[0].Key, Equals, tagging.Tags[0].Key)
+       c.Assert(getResult.Tags[0].Value, Equals, tagging.Tags[0].Value)
+
+       // delete BucketTagging
+       err = client.DeleteBucketTagging(bucketName, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       c.Assert(GetRequestId(respHeader) != "", Equals, true)
+
+       getResult, err = client.GetBucketTagging(bucketName, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       c.Assert(GetRequestId(respHeader) != "", Equals, true)
+       c.Assert(len(getResult.Tags), Equals, 0)
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestListBucketsTagging(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName1 := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName1)
+       c.Assert(err, IsNil)
+
+       bucketName2 := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName2)
+       c.Assert(err, IsNil)
+
+       // Bucket Tagging
+       var tagging Tagging
+       tagging.Tags = []Tag{Tag{Key: "testkey", Value: "testvalue"}}
+       err = client.SetBucketTagging(bucketName1, tagging)
+       c.Assert(err, IsNil)
+
+       // list bucket
+       listResult, err := client.ListBuckets(TagKey("testkey"))
+       c.Assert(err, IsNil)
+       c.Assert(len(listResult.Buckets), Equals, 1)
+       c.Assert(listResult.Buckets[0].Name, Equals, bucketName1)
+
+       client.DeleteBucket(bucketName1)
+       client.DeleteBucket(bucketName2)
+}
+
+func (s *OssClientSuite) TestGetBucketStat(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // put object
+       objectName := objectNamePrefix + RandLowStr(5)
+       err = bucket.PutObject(objectName, strings.NewReader(RandStr(10)))
+       c.Assert(err, IsNil)
+
+       bucket.DeleteObject(objectName)
+       err = bucket.PutObject(objectName, strings.NewReader(RandStr(10)))
+       c.Assert(err, IsNil)
+       bucket.DeleteObject(objectName)
+
+       _, err = client.GetBucketStat(bucketName)
+       c.Assert(err, IsNil)
+
+       client.DeleteBucket(bucketName)
+}
+
+func (s *OssBucketSuite) TestGetBucketVersioning(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+
+       var respHeader http.Header
+       err = client.CreateBucket(bucketName, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       c.Assert(GetRequestId(respHeader) != "", Equals, true)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // get bucket version success
+       versioningResult, err := client.GetBucketVersioning(bucketName, GetResponseHeader(&respHeader))
+       c.Assert(versioningResult.Status, Equals, "Enabled")
+       c.Assert(GetRequestId(respHeader) != "", Equals, true)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssClientSuite) TestBucketPolicy(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       var responseHeader http.Header
+       ret, err := client.GetBucketPolicy(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, NotNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       policyInfo := `
+       {
+               "Version":"1",
+               "Statement":[
+                       {
+                               "Action":[
+                                       "oss:GetObject",
+                                       "oss:PutObject"
+                               ],
+                               "Effect":"Deny",
+                               "Principal":"[123456790]",
+                               "Resource":["acs:oss:*:1234567890:*/*"]
+                       }
+               ]
+       }`
+
+       err = client.SetBucketPolicy(bucketName, policyInfo, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       ret, err = client.GetBucketPolicy(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       testLogger.Println("policy:", ret)
+       c.Assert(ret, Equals, policyInfo)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       err = client.DeleteBucketPolicy(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+       client.DeleteBucket(bucketName)
+}
+
+func (s *OssClientSuite) TestBucketPolicyNegative(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       var responseHeader http.Header
+       _, err = client.GetBucketPolicy(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, NotNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // Setting the Version is 2, this is error policy
+       errPolicy := `
+       {
+               "Version":"2",
+               "Statement":[
+                       {
+                               "Action":[
+                                       "oss:GetObject",
+                                       "oss:PutObject"
+                               ],
+                               "Effect":"Deny",
+                               "Principal":"[123456790]",
+                               "Resource":["acs:oss:*:1234567890:*/*"]
+                       }
+               ]
+       }`
+       err = client.SetBucketPolicy(bucketName, errPolicy, GetResponseHeader(&responseHeader))
+       c.Assert(err, NotNil)
+       testLogger.Println("err:", err)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       err = client.DeleteBucketPolicy(bucketName, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+
+       bucketNameEmpty := bucketNamePrefix + RandLowStr(5)
+       client.DeleteBucket(bucketNameEmpty)
+
+       err = client.DeleteBucketPolicy(bucketNameEmpty, GetResponseHeader(&responseHeader))
+       c.Assert(err, NotNil)
+       requestId = GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       client.DeleteBucket(bucketName)
+}
+
+func (s *OssClientSuite) TestSetBucketRequestPayment(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       reqPayConf := RequestPaymentConfiguration{
+               Payer: "Requester",
+       }
+       err = client.SetBucketRequestPayment(bucketName, reqPayConf)
+       c.Assert(err, IsNil)
+
+       ret, err := client.GetBucketRequestPayment(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(ret.Payer, Equals, "Requester")
+
+       client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestSetBucketRequestPaymentNegative(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       reqPayConf := RequestPaymentConfiguration{
+               Payer: "Requesterttttt", // this is a error configuration
+       }
+       err = client.SetBucketRequestPayment(bucketName, reqPayConf)
+       c.Assert(err, NotNil)
+
+       ret, err := client.GetBucketRequestPayment(bucketName)
+       c.Assert(err, IsNil)
+       c.Assert(ret.Payer, Equals, "BucketOwner")
+
+       client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestBucketQos(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       ret, err := client.GetUserQoSInfo()
+       c.Assert(err, IsNil)
+       testLogger.Println("QosInfo:", ret)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       _ = client.DeleteBucket(bucketName)
+
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       _, err = client.GetBucketQosInfo(bucketName)
+       c.Assert(err, NotNil)
+
+       // case 1 set BucketQoSConfiguration every member
+       five := 5
+       four := 4
+       totalQps := 200
+       qosConf := BucketQoSConfiguration{
+               TotalUploadBandwidth:      &five,
+               IntranetUploadBandwidth:   &four,
+               ExtranetUploadBandwidth:   &four,
+               TotalDownloadBandwidth:    &four,
+               IntranetDownloadBandwidth: &four,
+               ExtranetDownloadBandwidth: &four,
+               TotalQPS:                  &totalQps,
+               IntranetQPS:               &totalQps,
+               ExtranetQPS:               &totalQps,
+       }
+       var responseHeader http.Header
+       err = client.SetBucketQoSInfo(bucketName, qosConf, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       // wait a moment for configuration effect
+       time.Sleep(time.Second)
+
+       retQos, err := client.GetBucketQosInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       // set qosConf default value
+       qosConf.XMLName.Local = "QoSConfiguration"
+       c.Assert(struct2string(retQos, c), Equals, struct2string(qosConf, c))
+
+       // case 2 set BucketQoSConfiguration not every member
+       qosConfNo := BucketQoSConfiguration{
+               TotalUploadBandwidth:      &five,
+               IntranetUploadBandwidth:   &four,
+               ExtranetUploadBandwidth:   &four,
+               TotalDownloadBandwidth:    &four,
+               IntranetDownloadBandwidth: &four,
+               ExtranetDownloadBandwidth: &four,
+               TotalQPS:                  &totalQps,
+       }
+       err = client.SetBucketQoSInfo(bucketName, qosConfNo)
+       c.Assert(err, IsNil)
+
+       // wait a moment for configuration effect
+       time.Sleep(time.Second)
+
+       retQos, err = client.GetBucketQosInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       // set qosConfNo default value
+       qosConfNo.XMLName.Local = "QoSConfiguration"
+       defNum := -1
+       qosConfNo.IntranetQPS = &defNum
+       qosConfNo.ExtranetQPS = &defNum
+       c.Assert(struct2string(retQos, c), Equals, struct2string(qosConfNo, c))
+
+       err = client.DeleteBucketQosInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       // wait a moment for configuration effect
+       time.Sleep(time.Second)
+
+       _, err = client.GetBucketQosInfo(bucketName)
+       c.Assert(err, NotNil)
+
+       // this is a error qos configuration
+       to := *ret.TotalUploadBandwidth + 2
+       qosErrConf := BucketQoSConfiguration{
+               TotalUploadBandwidth:      &to, // this exceed user TotalUploadBandwidth
+               IntranetUploadBandwidth:   &four,
+               ExtranetUploadBandwidth:   &four,
+               TotalDownloadBandwidth:    &four,
+               IntranetDownloadBandwidth: &four,
+               ExtranetDownloadBandwidth: &four,
+               TotalQPS:                  &totalQps,
+               IntranetQPS:               &totalQps,
+               ExtranetQPS:               &totalQps,
+       }
+       err = client.SetBucketQoSInfo(bucketName, qosErrConf)
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucketQosInfo(bucketName)
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+// struct to string
+func struct2string(obj interface{}, c *C) string {
+       t := reflect.TypeOf(obj)
+       v := reflect.ValueOf(obj)
+
+       var data = make(map[string]interface{})
+       for i := 0; i < t.NumField(); i++ {
+               data[t.Field(i).Name] = v.Field(i).Interface()
+       }
+       str, err := json.Marshal(data)
+       c.Assert(err, IsNil)
+       return string(str)
+}
+
+type TestCredentials struct {
+}
+
+func (testCreInf *TestCredentials) GetAccessKeyID() string {
+       return os.Getenv("OSS_TEST_ACCESS_KEY_ID")
+}
+
+func (testCreInf *TestCredentials) GetAccessKeySecret() string {
+       return os.Getenv("OSS_TEST_ACCESS_KEY_SECRET")
+}
+
+func (testCreInf *TestCredentials) GetSecurityToken() string {
+       return ""
+}
+
+type TestCredentialsProvider struct {
+}
+
+func (testInfBuild *TestCredentialsProvider) GetCredentials() Credentials {
+       return &TestCredentials{}
+}
+
+func (s *OssClientSuite) TestClientCredentialInfBuild(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       var defaultBuild TestCredentialsProvider
+       client, err := New(endpoint, "", "", SetCredentialsProvider(&defaultBuild))
+       c.Assert(err, IsNil)
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestClientSetLocalIpError(c *C) {
+       // create client and bucket
+       ipAddr, err := net.ResolveIPAddr("ip", "127.0.0.1")
+       c.Assert(err, IsNil)
+       localTCPAddr := &(net.TCPAddr{IP: ipAddr.IP})
+       client, err := New(endpoint, accessID, accessKey, SetLocalAddr(localTCPAddr))
+       c.Assert(err, IsNil)
+
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, NotNil)
+}
+
+func (s *OssClientSuite) TestClientSetLocalIpSuccess(c *C) {
+       //get local ip
+       conn, err := net.Dial("udp", "8.8.8.8:80")
+       c.Assert(err, IsNil)
+       localAddr := conn.LocalAddr().(*net.UDPAddr)
+       localIp := localAddr.IP.String()
+       conn.Close()
+
+       ipAddr, err := net.ResolveIPAddr("ip", localIp)
+       c.Assert(err, IsNil)
+       localTCPAddr := &(net.TCPAddr{IP: ipAddr.IP})
+       client, err := New(endpoint, accessID, accessKey, SetLocalAddr(localTCPAddr))
+       c.Assert(err, IsNil)
+
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestCreateBucketInvalidName
+func (s *OssClientSuite) TestCreateBucketInvalidName(c *C) {
+       var bucketNameTest = "-" + bucketNamePrefix + RandLowStr(6)
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       // Create
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, NotNil)
+}
+
+// TestClientProcessEndpointSuccess
+func (s *OssClientSuite) TestClientProcessEndpointSuccess(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       testEndpoint := endpoint + "/" + "sina.com" + "?" + "para=abc"
+
+       client, err := New(testEndpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Create
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // delete
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestClientProcessEndpointSuccess
+func (s *OssClientSuite) TestClientProcessEndpointError(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+
+       testEndpoint := "https://127.0.0.1/" + endpoint
+
+       client, err := New(testEndpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // Create
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, NotNil)
+}
+
+// TestClientBucketError
+func (s *OssClientSuite) TestClientBucketError(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := "-" + RandLowStr(5)
+       _, err = client.Bucket(bucketName)
+       c.Assert(err, NotNil)
+}
+
+func (s *OssClientSuite) TestSetBucketInventory(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // encryption config
+       var invSseOss InvSseOss
+       invSseKms := InvSseKms{
+               KmsId: "keyId",
+       }
+       var invEncryption InvEncryption
+
+       bl := true
+       // not any encryption
+       invConfig := InventoryConfiguration{
+               Id:        "report1",
+               IsEnabled: &bl,
+               Prefix:    "filterPrefix/",
+               OSSBucketDestination: OSSBucketDestination{
+                       Format:    "CSV",
+                       AccountId: accountID,
+                       RoleArn:   stsARN,
+                       Bucket:    "acs:oss:::" + bucketName,
+                       Prefix:    "prefix1",
+               },
+               Frequency:              "Daily",
+               IncludedObjectVersions: "All",
+               OptionalFields: OptionalFields{
+                       Field: []string{
+                               "Size", "LastModifiedDate", "ETag", "StorageClass", "IsMultipartUploaded", "EncryptionStatus",
+                       },
+               },
+       }
+
+       // case 1: not any encryption
+       err = client.SetBucketInventory(bucketName, invConfig)
+       c.Assert(err, IsNil)
+
+       // case 2: use kms encryption
+       invConfig.Id = "report2"
+       invEncryption.SseKms = &invSseKms
+       invEncryption.SseOss = nil
+       invConfig.OSSBucketDestination.Encryption = &invEncryption
+       err = client.SetBucketInventory(bucketName, invConfig)
+       c.Assert(err, IsNil)
+
+       // case 3: use SseOss encryption
+       invConfig.Id = "report3"
+       invEncryption.SseKms = nil
+       invEncryption.SseOss = &invSseOss
+       invConfig.OSSBucketDestination.Encryption = &invEncryption
+       err = client.SetBucketInventory(bucketName, invConfig)
+       c.Assert(err, IsNil)
+
+       //case 4: use two type encryption
+       invConfig.Id = "report4"
+       invEncryption.SseKms = &invSseKms
+       invEncryption.SseOss = &invSseOss
+       invConfig.OSSBucketDestination.Encryption = &invEncryption
+       err = client.SetBucketInventory(bucketName, invConfig)
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestBucketInventory(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bl := true
+       invConfig := InventoryConfiguration{
+               Id:        "report1",
+               IsEnabled: &bl,
+               Prefix:    "filterPrefix/",
+               OSSBucketDestination: OSSBucketDestination{
+                       Format:    "CSV",
+                       AccountId: accountID,
+                       RoleArn:   stsARN,
+                       Bucket:    "acs:oss:::" + bucketName,
+                       Prefix:    "prefix1",
+               },
+               Frequency:              "Daily",
+               IncludedObjectVersions: "All",
+               OptionalFields: OptionalFields{
+                       Field: []string{
+                               "Size", "LastModifiedDate", "ETag", "StorageClass", "IsMultipartUploaded", "EncryptionStatus",
+                       },
+               },
+       }
+
+       // case 1: test SetBucketInventory
+       err = client.SetBucketInventory(bucketName, invConfig)
+       c.Assert(err, IsNil)
+
+       // case 2: test GetBucketInventory
+       out, err := client.GetBucketInventory(bucketName, "report1")
+       c.Assert(err, IsNil)
+       invConfig.XMLName.Local = "InventoryConfiguration"
+       invConfig.OSSBucketDestination.XMLName.Local = "OSSBucketDestination"
+       invConfig.OptionalFields.XMLName.Local = "OptionalFields"
+       c.Assert(struct2string(invConfig, c), Equals, struct2string(out, c))
+
+       // case 3: test ListBucketInventory
+       invConfig2 := InventoryConfiguration{
+               Id:        "report2",
+               IsEnabled: &bl,
+               Prefix:    "filterPrefix/",
+               OSSBucketDestination: OSSBucketDestination{
+                       Format:    "CSV",
+                       AccountId: accountID,
+                       RoleArn:   stsARN,
+                       Bucket:    "acs:oss:::" + bucketName,
+                       Prefix:    "prefix1",
+               },
+               Frequency:              "Daily",
+               IncludedObjectVersions: "All",
+               OptionalFields: OptionalFields{
+                       Field: []string{
+                               "Size", "LastModifiedDate", "ETag", "StorageClass", "IsMultipartUploaded", "EncryptionStatus",
+                       },
+               },
+       }
+       invConfig2.XMLName.Local = "InventoryConfiguration"
+       invConfig2.OSSBucketDestination.XMLName.Local = "OSSBucketDestination"
+       invConfig2.OptionalFields.XMLName.Local = "OptionalFields"
+
+       err = client.SetBucketInventory(bucketName, invConfig2)
+       c.Assert(err, IsNil)
+
+       listInvConf, err := client.ListBucketInventory(bucketName, "", Marker("report1"), MaxKeys(2))
+       c.Assert(err, IsNil)
+       var listInvLocal ListInventoryConfigurationsResult
+       listInvLocal.InventoryConfiguration = []InventoryConfiguration{
+               invConfig,
+               invConfig2,
+       }
+       bo := false
+       listInvLocal.IsTruncated = &bo
+       listInvLocal.XMLName.Local = "ListInventoryConfigurationsResult"
+       c.Assert(struct2string(listInvLocal, c), Equals, struct2string(listInvConf, c))
+
+       for i := 3; i < 109; i++ {
+               invConfig2 := InventoryConfiguration{
+                       Id:        "report" + strconv.Itoa(i),
+                       IsEnabled: &bl,
+                       Prefix:    "filterPrefix/",
+                       OSSBucketDestination: OSSBucketDestination{
+                               Format:    "CSV",
+                               AccountId: accountID,
+                               RoleArn:   stsARN,
+                               Bucket:    "acs:oss:::" + bucketName,
+                               Prefix:    "prefix1",
+                       },
+                       Frequency:              "Daily",
+                       IncludedObjectVersions: "All",
+                       OptionalFields: OptionalFields{
+                               Field: []string{
+                                       "Size", "LastModifiedDate", "ETag", "StorageClass", "IsMultipartUploaded", "EncryptionStatus",
+                               },
+                       },
+               }
+               err = client.SetBucketInventory(bucketName, invConfig2)
+               c.Assert(err, IsNil)
+       }
+       token := ""
+       for {
+               listInvConf1, err := client.ListBucketInventory(bucketName, token)
+               c.Assert(err, IsNil)
+               token = listInvConf1.NextContinuationToken
+               testLogger.Println(listInvConf1.NextContinuationToken, *listInvConf1.IsTruncated, token)
+               if *listInvConf1.IsTruncated == false {
+                       break
+               } else {
+                       c.Assert(listInvConf1.NextContinuationToken, Equals, "report91")
+               }
+       }
+
+       // case 4: test DeleteBucketInventory
+       for i := 1; i < 109; i++ {
+               err = client.DeleteBucketInventory(bucketName, "report"+strconv.Itoa(i))
+               c.Assert(err, IsNil)
+       }
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestBucketInventoryNegative(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bl := true
+       invConfigErr := InventoryConfiguration{
+               Id:        "report1",
+               IsEnabled: &bl,
+               Prefix:    "filterPrefix/",
+               OSSBucketDestination: OSSBucketDestination{
+                       Format:    "CSV",
+                       AccountId: accountID,
+                       RoleArn:   stsARN,
+                       Bucket:    "test",
+                       Prefix:    "prefix1",
+               },
+               Frequency:              "Daily",
+               IncludedObjectVersions: "All",
+               OptionalFields: OptionalFields{
+                       Field: []string{
+                               "Size", "LastModifiedDate", "ETag", "StorageClass", "IsMultipartUploaded", "EncryptionStatus",
+                       },
+               },
+       }
+       // case 1: test SetBucketInventory
+       err = client.SetBucketInventory(bucketName, invConfigErr)
+       c.Assert(err, NotNil)
+
+       // case 2: test GetBucketInventory
+       _, err = client.GetBucketInventory(bucketName, "report1")
+       c.Assert(err, NotNil)
+
+       // case 3: test ListBucketInventory
+       _, err = client.ListBucketInventory(bucketName, "", Marker("report1"), MaxKeys(2))
+       c.Assert(err, NotNil)
+
+       // case 4: test DeleteBucketInventory
+       err = client.DeleteBucketInventory(bucketName, "report1")
+       c.Assert(err, IsNil)
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestBucketAsyncTask(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(5)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandLowStr(6)
+
+       // set asyn task,IgnoreSameKey is false
+       asynConf := AsyncFetchTaskConfiguration{
+               Url:           "http://www.baidu.com",
+               Object:        objectName,
+               Host:          "",
+               ContentMD5:    "",
+               Callback:      "",
+               StorageClass:  "",
+               IgnoreSameKey: false,
+       }
+
+       asynResult, err := client.SetBucketAsyncTask(bucketName, asynConf)
+       c.Assert(err, IsNil)
+       c.Assert(len(asynResult.TaskId) > 0, Equals, true)
+
+       // get asyn task
+       asynTask, err := client.GetBucketAsyncTask(bucketName, asynResult.TaskId)
+       c.Assert(err, IsNil)
+       c.Assert(asynResult.TaskId, Equals, asynTask.TaskId)
+       c.Assert(len(asynTask.State) > 0, Equals, true)
+       c.Assert(asynConf.Url, Equals, asynTask.TaskInfo.Url)
+       c.Assert(asynConf.Object, Equals, asynTask.TaskInfo.Object)
+       c.Assert(asynConf.Callback, Equals, asynTask.TaskInfo.Callback)
+       c.Assert(asynConf.IgnoreSameKey, Equals, asynTask.TaskInfo.IgnoreSameKey)
+
+       // test again,IgnoreSameKey is true
+       asynConf.IgnoreSameKey = true
+       asynResult, err = client.SetBucketAsyncTask(bucketName, asynConf)
+       c.Assert(err, IsNil)
+       c.Assert(len(asynResult.TaskId) > 0, Equals, true)
+
+       asynTask, err = client.GetBucketAsyncTask(bucketName, asynResult.TaskId)
+       c.Assert(asynConf.IgnoreSameKey, Equals, asynTask.TaskInfo.IgnoreSameKey)
+
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssClientSuite) TestClientOptionHeader(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+
+       var respHeader http.Header
+       err = client.CreateBucket(bucketName, GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+       c.Assert(GetRequestId(respHeader) != "", Equals, true)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // get bucket version success,use payer
+       options := []Option{RequestPayer(BucketOwner), GetResponseHeader(&respHeader)}
+       versioningResult, err := client.GetBucketVersioning(bucketName, options...)
+       c.Assert(versioningResult.Status, Equals, "Enabled")
+       c.Assert(GetRequestId(respHeader) != "", Equals, true)
+
+       //list buckets,use payer
+       _, err = client.ListBuckets(options...)
+       c.Assert(err, IsNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// compare with go1.7
+func compareVersion(goVersion string) bool {
+       nowVersion := runtime.Version()
+       nowVersion = strings.Replace(nowVersion, "go", "", -1)
+       pSlice1 := strings.Split(goVersion, ".")
+       pSlice2 := strings.Split(nowVersion, ".")
+       for k, v := range pSlice2 {
+               n2, _ := strconv.Atoi(string(v))
+               n1, _ := strconv.Atoi(string(pSlice1[k]))
+               if n2 > n1 {
+                       return true
+               }
+               if n2 < n1 {
+                       return false
+               }
+       }
+       return true
+}
+
+func homeHandler(w http.ResponseWriter, r *http.Request) {
+       http.Redirect(w, r, "/redirectTo", http.StatusFound)
+}
+func targetHandler(w http.ResponseWriter, r *http.Request) {
+       fmt.Fprintf(w, "You have been redirected here!")
+}
+
+func (s *OssClientSuite) TestClientRedirect(c *C) {
+       // must go1.7.0 onward
+       if !compareVersion("1.7.0") {
+               return
+       }
+
+       // get port
+       rand.Seed(time.Now().Unix())
+       port := 10000 + rand.Intn(10000)
+
+       // start http server
+       httpAddr := fmt.Sprintf("127.0.0.1:%d", port)
+       mux := http.NewServeMux()
+       mux.HandleFunc("/redirectTo", targetHandler)
+       mux.HandleFunc("/", homeHandler)
+       svr := &http.Server{
+               Addr:           httpAddr,
+               ReadTimeout:    10 * time.Second,
+               WriteTimeout:   10 * time.Second,
+               MaxHeaderBytes: 1 << 20,
+               Handler:        mux,
+       }
+
+       go func() {
+               svr.ListenAndServe()
+       }()
+
+       url := "http://" + httpAddr
+
+       // create client 1,redirect disable
+       client1, err := New(endpoint, accessID, accessKey, RedirectEnabled(false))
+       resp, err := client1.Conn.client.Get(url)
+       c.Assert(err, IsNil)
+       c.Assert(resp.StatusCode, Equals, http.StatusFound)
+       resp.Body.Close()
+
+       // create client2, redirect enabled
+       client2, err := New(endpoint, accessID, accessKey, RedirectEnabled(true))
+       resp, err = client2.Conn.client.Get(url)
+       c.Assert(err, IsNil)
+       c.Assert(resp.StatusCode, Equals, 200)
+       data, err := ioutil.ReadAll(resp.Body)
+       c.Assert(string(data), Equals, "You have been redirected here!")
+       resp.Body.Close()
+}
+
+// TestInitiateBucketWormSuccess
+func (s *OssClientSuite) TestInitiateBucketWormSuccess(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // InitiateBucketWorm
+       wormId, err := client.InitiateBucketWorm(bucketNameTest, 10)
+       c.Assert(err, IsNil)
+       c.Assert(len(wormId) > 0, Equals, true)
+
+       // GetBucketWorm
+       wormConfig, err := client.GetBucketWorm(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(wormConfig.WormId, Equals, wormId)
+       c.Assert(wormConfig.State, Equals, "InProgress")
+       c.Assert(wormConfig.RetentionPeriodInDays, Equals, 10)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestInitiateBucketWormFailure
+func (s *OssClientSuite) TestInitiateBucketWormFailure(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       // bucket not exist
+       wormId, err := client.InitiateBucketWorm(bucketNameTest, 10)
+       c.Assert(err, NotNil)
+       c.Assert(len(wormId), Equals, 0)
+}
+
+// TestAbortBucketWorm
+func (s *OssClientSuite) TestAbortBucketWorm(c *C) {
+       var bucketNameTest = bucketNamePrefix + RandLowStr(6)
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // InitiateBucketWorm
+       wormId, err := client.InitiateBucketWorm(bucketNameTest, 10)
+       c.Assert(err, IsNil)
+       c.Assert(len(wormId) > 0, Equals, true)
+
+       // GetBucketWorm success
+       wormConfig, err := client.GetBucketWorm(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(wormConfig.WormId, Equals, wormId)
+       c.Assert(wormConfig.State, Equals, "InProgress")
+       c.Assert(wormConfig.RetentionPeriodInDays, Equals, 10)
+
+       // abort worm
+       err = client.AbortBucketWorm(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // GetBucketWorm failure
+       _, err = client.GetBucketWorm(bucketNameTest)
+       c.Assert(err, NotNil)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestCompleteBucketWorm
+func (s *OssClientSuite) TestCompleteBucketWorm(c *C) {
+       var bucketNameTest = bucketNamePrefix + "-worm-" + RandLowStr(6)
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // InitiateBucketWorm
+       wormId, err := client.InitiateBucketWorm(bucketNameTest, 1)
+       c.Assert(err, IsNil)
+       c.Assert(len(wormId) > 0, Equals, true)
+
+       // GetBucketWorm
+       wormConfig, err := client.GetBucketWorm(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(wormConfig.WormId, Equals, wormId)
+       c.Assert(wormConfig.State, Equals, "InProgress")
+       c.Assert(wormConfig.RetentionPeriodInDays, Equals, 1)
+
+       // CompleteBucketWorm
+       err = client.CompleteBucketWorm(bucketNameTest, wormId)
+       c.Assert(err, IsNil)
+
+       // GetBucketWorm again
+       wormConfig, err = client.GetBucketWorm(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(wormConfig.WormId, Equals, wormId)
+       c.Assert(wormConfig.State, Equals, "Locked")
+       c.Assert(wormConfig.RetentionPeriodInDays, Equals, 1)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
+
+// TestExtendBucketWorm
+func (s *OssClientSuite) TestExtendBucketWorm(c *C) {
+       var bucketNameTest = bucketNamePrefix + "-worm-" + RandLowStr(6)
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       err = client.CreateBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+
+       // InitiateBucketWorm
+       wormId, err := client.InitiateBucketWorm(bucketNameTest, 1)
+       c.Assert(err, IsNil)
+       c.Assert(len(wormId) > 0, Equals, true)
+
+       // CompleteBucketWorm
+       err = client.CompleteBucketWorm(bucketNameTest, wormId)
+       c.Assert(err, IsNil)
+
+       // GetBucketWorm
+       wormConfig, err := client.GetBucketWorm(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(wormConfig.WormId, Equals, wormId)
+       c.Assert(wormConfig.State, Equals, "Locked")
+       c.Assert(wormConfig.RetentionPeriodInDays, Equals, 1)
+
+       // CompleteBucketWorm
+       err = client.ExtendBucketWorm(bucketNameTest, 2, wormId)
+       c.Assert(err, IsNil)
+
+       // GetBucketWorm again
+       wormConfig, err = client.GetBucketWorm(bucketNameTest)
+       c.Assert(err, IsNil)
+       c.Assert(wormConfig.WormId, Equals, wormId)
+       c.Assert(wormConfig.State, Equals, "Locked")
+       c.Assert(wormConfig.RetentionPeriodInDays, Equals, 2)
+
+       err = client.DeleteBucket(bucketNameTest)
+       c.Assert(err, IsNil)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conf.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conf.go
new file mode 100644 (file)
index 0000000..55e5e37
--- /dev/null
@@ -0,0 +1,185 @@
+package oss
+
+import (
+       "bytes"
+       "fmt"
+       "log"
+       "net"
+       "os"
+       "time"
+)
+
+// Define the level of the output log
+const (
+       LogOff = iota
+       Error
+       Warn
+       Info
+       Debug
+)
+
+// LogTag Tag for each level of log
+var LogTag = []string{"[error]", "[warn]", "[info]", "[debug]"}
+
+// HTTPTimeout defines HTTP timeout.
+type HTTPTimeout struct {
+       ConnectTimeout   time.Duration
+       ReadWriteTimeout time.Duration
+       HeaderTimeout    time.Duration
+       LongTimeout      time.Duration
+       IdleConnTimeout  time.Duration
+}
+
+// HTTPMaxConns defines max idle connections and max idle connections per host
+type HTTPMaxConns struct {
+       MaxIdleConns        int
+       MaxIdleConnsPerHost int
+}
+
+// CredentialInf is interface for get AccessKeyID,AccessKeySecret,SecurityToken
+type Credentials interface {
+       GetAccessKeyID() string
+       GetAccessKeySecret() string
+       GetSecurityToken() string
+}
+
+// CredentialInfBuild is interface for get CredentialInf
+type CredentialsProvider interface {
+       GetCredentials() Credentials
+}
+
+type defaultCredentials struct {
+       config *Config
+}
+
+func (defCre *defaultCredentials) GetAccessKeyID() string {
+       return defCre.config.AccessKeyID
+}
+
+func (defCre *defaultCredentials) GetAccessKeySecret() string {
+       return defCre.config.AccessKeySecret
+}
+
+func (defCre *defaultCredentials) GetSecurityToken() string {
+       return defCre.config.SecurityToken
+}
+
+type defaultCredentialsProvider struct {
+       config *Config
+}
+
+func (defBuild *defaultCredentialsProvider) GetCredentials() Credentials {
+       return &defaultCredentials{config: defBuild.config}
+}
+
+// Config defines oss configuration
+type Config struct {
+       Endpoint            string              // OSS endpoint
+       AccessKeyID         string              // AccessId
+       AccessKeySecret     string              // AccessKey
+       RetryTimes          uint                // Retry count by default it's 5.
+       UserAgent           string              // SDK name/version/system information
+       IsDebug             bool                // Enable debug mode. Default is false.
+       Timeout             uint                // Timeout in seconds. By default it's 60.
+       SecurityToken       string              // STS Token
+       IsCname             bool                // If cname is in the endpoint.
+       HTTPTimeout         HTTPTimeout         // HTTP timeout
+       HTTPMaxConns        HTTPMaxConns        // Http max connections
+       IsUseProxy          bool                // Flag of using proxy.
+       ProxyHost           string              // Flag of using proxy host.
+       IsAuthProxy         bool                // Flag of needing authentication.
+       ProxyUser           string              // Proxy user
+       ProxyPassword       string              // Proxy password
+       IsEnableMD5         bool                // Flag of enabling MD5 for upload.
+       MD5Threshold        int64               // Memory footprint threshold for each MD5 computation (16MB is the default), in byte. When the data is more than that, temp file is used.
+       IsEnableCRC         bool                // Flag of enabling CRC for upload.
+       LogLevel            int                 // Log level
+       Logger              *log.Logger         // For write log
+       UploadLimitSpeed    int                 // Upload limit speed:KB/s, 0 is unlimited
+       UploadLimiter       *OssLimiter         // Bandwidth limit reader for upload
+       CredentialsProvider CredentialsProvider // User provides interface to get AccessKeyID, AccessKeySecret, SecurityToken
+       LocalAddr           net.Addr            // local client host info
+       UserSetUa           bool                // UserAgent is set by user or not
+       AuthVersion         AuthVersionType     //  v1 or v2 signature,default is v1
+       AdditionalHeaders   []string            //  special http headers needed to be sign
+       RedirectEnabled     bool                //  only effective from go1.7 onward, enable http redirect or not
+}
+
+// LimitUploadSpeed uploadSpeed:KB/s, 0 is unlimited,default is 0
+func (config *Config) LimitUploadSpeed(uploadSpeed int) error {
+       if uploadSpeed < 0 {
+               return fmt.Errorf("invalid argument, the value of uploadSpeed is less than 0")
+       } else if uploadSpeed == 0 {
+               config.UploadLimitSpeed = 0
+               config.UploadLimiter = nil
+               return nil
+       }
+
+       var err error
+       config.UploadLimiter, err = GetOssLimiter(uploadSpeed)
+       if err == nil {
+               config.UploadLimitSpeed = uploadSpeed
+       }
+       return err
+}
+
+// WriteLog output log function
+func (config *Config) WriteLog(LogLevel int, format string, a ...interface{}) {
+       if config.LogLevel < LogLevel || config.Logger == nil {
+               return
+       }
+
+       var logBuffer bytes.Buffer
+       logBuffer.WriteString(LogTag[LogLevel-1])
+       logBuffer.WriteString(fmt.Sprintf(format, a...))
+       config.Logger.Printf("%s", logBuffer.String())
+}
+
+// for get Credentials
+func (config *Config) GetCredentials() Credentials {
+       return config.CredentialsProvider.GetCredentials()
+}
+
+// getDefaultOssConfig gets the default configuration.
+func getDefaultOssConfig() *Config {
+       config := Config{}
+
+       config.Endpoint = ""
+       config.AccessKeyID = ""
+       config.AccessKeySecret = ""
+       config.RetryTimes = 5
+       config.IsDebug = false
+       config.UserAgent = userAgent()
+       config.Timeout = 60 // Seconds
+       config.SecurityToken = ""
+       config.IsCname = false
+
+       config.HTTPTimeout.ConnectTimeout = time.Second * 30   // 30s
+       config.HTTPTimeout.ReadWriteTimeout = time.Second * 60 // 60s
+       config.HTTPTimeout.HeaderTimeout = time.Second * 60    // 60s
+       config.HTTPTimeout.LongTimeout = time.Second * 300     // 300s
+       config.HTTPTimeout.IdleConnTimeout = time.Second * 50  // 50s
+       config.HTTPMaxConns.MaxIdleConns = 100
+       config.HTTPMaxConns.MaxIdleConnsPerHost = 100
+
+       config.IsUseProxy = false
+       config.ProxyHost = ""
+       config.IsAuthProxy = false
+       config.ProxyUser = ""
+       config.ProxyPassword = ""
+
+       config.MD5Threshold = 16 * 1024 * 1024 // 16MB
+       config.IsEnableMD5 = false
+       config.IsEnableCRC = true
+
+       config.LogLevel = LogOff
+       config.Logger = log.New(os.Stdout, "", log.LstdFlags)
+
+       provider := &defaultCredentialsProvider{config: &config}
+       config.CredentialsProvider = provider
+
+       config.AuthVersion = AuthV1
+       config.RedirectEnabled = true
+
+       return &config
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn.go
new file mode 100644 (file)
index 0000000..6e9db0f
--- /dev/null
@@ -0,0 +1,793 @@
+package oss
+
+import (
+       "bytes"
+       "crypto/md5"
+       "encoding/base64"
+       "encoding/json"
+       "encoding/xml"
+       "fmt"
+       "hash"
+       "io"
+       "io/ioutil"
+       "net"
+       "net/http"
+       "net/url"
+       "os"
+       "sort"
+       "strconv"
+       "strings"
+       "time"
+)
+
+// Conn defines OSS Conn
+type Conn struct {
+       config *Config
+       url    *urlMaker
+       client *http.Client
+}
+
+var signKeyList = []string{"acl", "uploads", "location", "cors",
+       "logging", "website", "referer", "lifecycle",
+       "delete", "append", "tagging", "objectMeta",
+       "uploadId", "partNumber", "security-token",
+       "position", "img", "style", "styleName",
+       "replication", "replicationProgress",
+       "replicationLocation", "cname", "bucketInfo",
+       "comp", "qos", "live", "status", "vod",
+       "startTime", "endTime", "symlink",
+       "x-oss-process", "response-content-type", "x-oss-traffic-limit",
+       "response-content-language", "response-expires",
+       "response-cache-control", "response-content-disposition",
+       "response-content-encoding", "udf", "udfName", "udfImage",
+       "udfId", "udfImageDesc", "udfApplication", "comp",
+       "udfApplicationLog", "restore", "callback", "callback-var", "qosInfo",
+       "policy", "stat", "encryption", "versions", "versioning", "versionId", "requestPayment",
+       "x-oss-request-payer", "sequential",
+       "inventory", "inventoryId", "continuation-token", "asyncFetch",
+       "worm", "wormId", "wormExtend"}
+
+// init initializes Conn
+func (conn *Conn) init(config *Config, urlMaker *urlMaker, client *http.Client) error {
+       if client == nil {
+               // New transport
+               transport := newTransport(conn, config)
+
+               // Proxy
+               if conn.config.IsUseProxy {
+                       proxyURL, err := url.Parse(config.ProxyHost)
+                       if err != nil {
+                               return err
+                       }
+                       if config.IsAuthProxy {
+                               if config.ProxyPassword != "" {
+                                       proxyURL.User = url.UserPassword(config.ProxyUser, config.ProxyPassword)
+                               } else {
+                                       proxyURL.User = url.User(config.ProxyUser)
+                               }
+                       }
+                       transport.Proxy = http.ProxyURL(proxyURL)
+               }
+               client = &http.Client{Transport: transport}
+               if !config.RedirectEnabled {
+                       disableHTTPRedirect(client)
+               }
+       }
+
+       conn.config = config
+       conn.url = urlMaker
+       conn.client = client
+
+       return nil
+}
+
+// Do sends request and returns the response
+func (conn Conn) Do(method, bucketName, objectName string, params map[string]interface{}, headers map[string]string,
+       data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) {
+       urlParams := conn.getURLParams(params)
+       subResource := conn.getSubResource(params)
+       uri := conn.url.getURL(bucketName, objectName, urlParams)
+       resource := conn.getResource(bucketName, objectName, subResource)
+       return conn.doRequest(method, uri, resource, headers, data, initCRC, listener)
+}
+
+// DoURL sends the request with signed URL and returns the response result.
+func (conn Conn) DoURL(method HTTPMethod, signedURL string, headers map[string]string,
+       data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) {
+       // Get URI from signedURL
+       uri, err := url.ParseRequestURI(signedURL)
+       if err != nil {
+               return nil, err
+       }
+
+       m := strings.ToUpper(string(method))
+       req := &http.Request{
+               Method:     m,
+               URL:        uri,
+               Proto:      "HTTP/1.1",
+               ProtoMajor: 1,
+               ProtoMinor: 1,
+               Header:     make(http.Header),
+               Host:       uri.Host,
+       }
+
+       tracker := &readerTracker{completedBytes: 0}
+       fd, crc := conn.handleBody(req, data, initCRC, listener, tracker)
+       if fd != nil {
+               defer func() {
+                       fd.Close()
+                       os.Remove(fd.Name())
+               }()
+       }
+
+       if conn.config.IsAuthProxy {
+               auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword
+               basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
+               req.Header.Set("Proxy-Authorization", basic)
+       }
+
+       req.Header.Set(HTTPHeaderHost, req.Host)
+       req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent)
+
+       if headers != nil {
+               for k, v := range headers {
+                       req.Header.Set(k, v)
+               }
+       }
+
+       // Transfer started
+       event := newProgressEvent(TransferStartedEvent, 0, req.ContentLength, 0)
+       publishProgress(listener, event)
+
+       if conn.config.LogLevel >= Debug {
+               conn.LoggerHTTPReq(req)
+       }
+
+       resp, err := conn.client.Do(req)
+       if err != nil {
+               // Transfer failed
+               event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength, 0)
+               publishProgress(listener, event)
+               conn.config.WriteLog(Debug, "[Resp:%p]http error:%s\n", req, err.Error())
+               return nil, err
+       }
+
+       if conn.config.LogLevel >= Debug {
+               //print out http resp
+               conn.LoggerHTTPResp(req, resp)
+       }
+
+       // Transfer completed
+       event = newProgressEvent(TransferCompletedEvent, tracker.completedBytes, req.ContentLength, 0)
+       publishProgress(listener, event)
+
+       return conn.handleResponse(resp, crc)
+}
+
+func (conn Conn) getURLParams(params map[string]interface{}) string {
+       // Sort
+       keys := make([]string, 0, len(params))
+       for k := range params {
+               keys = append(keys, k)
+       }
+       sort.Strings(keys)
+
+       // Serialize
+       var buf bytes.Buffer
+       for _, k := range keys {
+               if buf.Len() > 0 {
+                       buf.WriteByte('&')
+               }
+               buf.WriteString(url.QueryEscape(k))
+               if params[k] != nil {
+                       buf.WriteString("=" + strings.Replace(url.QueryEscape(params[k].(string)), "+", "%20", -1))
+               }
+       }
+
+       return buf.String()
+}
+
+func (conn Conn) getSubResource(params map[string]interface{}) string {
+       // Sort
+       keys := make([]string, 0, len(params))
+       signParams := make(map[string]string)
+       for k := range params {
+               if conn.config.AuthVersion == AuthV2 {
+                       encodedKey := url.QueryEscape(k)
+                       keys = append(keys, encodedKey)
+                       if params[k] != nil && params[k] != "" {
+                               signParams[encodedKey] = strings.Replace(url.QueryEscape(params[k].(string)), "+", "%20", -1)
+                       }
+               } else if conn.isParamSign(k) {
+                       keys = append(keys, k)
+                       if params[k] != nil {
+                               signParams[k] = params[k].(string)
+                       }
+               }
+       }
+       sort.Strings(keys)
+
+       // Serialize
+       var buf bytes.Buffer
+       for _, k := range keys {
+               if buf.Len() > 0 {
+                       buf.WriteByte('&')
+               }
+               buf.WriteString(k)
+               if _, ok := signParams[k]; ok {
+                       buf.WriteString("=" + signParams[k])
+               }
+       }
+       return buf.String()
+}
+
+func (conn Conn) isParamSign(paramKey string) bool {
+       for _, k := range signKeyList {
+               if paramKey == k {
+                       return true
+               }
+       }
+       return false
+}
+
+// getResource gets canonicalized resource
+func (conn Conn) getResource(bucketName, objectName, subResource string) string {
+       if subResource != "" {
+               subResource = "?" + subResource
+       }
+       if bucketName == "" {
+               if conn.config.AuthVersion == AuthV2 {
+                       return url.QueryEscape("/") + subResource
+               }
+               return fmt.Sprintf("/%s%s", bucketName, subResource)
+       }
+       if conn.config.AuthVersion == AuthV2 {
+               return url.QueryEscape("/"+bucketName+"/") + strings.Replace(url.QueryEscape(objectName), "+", "%20", -1) + subResource
+       }
+       return fmt.Sprintf("/%s/%s%s", bucketName, objectName, subResource)
+}
+
+func (conn Conn) doRequest(method string, uri *url.URL, canonicalizedResource string, headers map[string]string,
+       data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) {
+       method = strings.ToUpper(method)
+       req := &http.Request{
+               Method:     method,
+               URL:        uri,
+               Proto:      "HTTP/1.1",
+               ProtoMajor: 1,
+               ProtoMinor: 1,
+               Header:     make(http.Header),
+               Host:       uri.Host,
+       }
+
+       tracker := &readerTracker{completedBytes: 0}
+       fd, crc := conn.handleBody(req, data, initCRC, listener, tracker)
+       if fd != nil {
+               defer func() {
+                       fd.Close()
+                       os.Remove(fd.Name())
+               }()
+       }
+
+       if conn.config.IsAuthProxy {
+               auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword
+               basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
+               req.Header.Set("Proxy-Authorization", basic)
+       }
+
+       date := time.Now().UTC().Format(http.TimeFormat)
+       req.Header.Set(HTTPHeaderDate, date)
+       req.Header.Set(HTTPHeaderHost, req.Host)
+       req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent)
+
+       akIf := conn.config.GetCredentials()
+       if akIf.GetSecurityToken() != "" {
+               req.Header.Set(HTTPHeaderOssSecurityToken, akIf.GetSecurityToken())
+       }
+
+       if headers != nil {
+               for k, v := range headers {
+                       req.Header.Set(k, v)
+               }
+       }
+
+       conn.signHeader(req, canonicalizedResource)
+
+       // Transfer started
+       event := newProgressEvent(TransferStartedEvent, 0, req.ContentLength, 0)
+       publishProgress(listener, event)
+
+       if conn.config.LogLevel >= Debug {
+               conn.LoggerHTTPReq(req)
+       }
+
+       resp, err := conn.client.Do(req)
+
+       if err != nil {
+               // Transfer failed
+               event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength, 0)
+               publishProgress(listener, event)
+               conn.config.WriteLog(Debug, "[Resp:%p]http error:%s\n", req, err.Error())
+               return nil, err
+       }
+
+       if conn.config.LogLevel >= Debug {
+               //print out http resp
+               conn.LoggerHTTPResp(req, resp)
+       }
+
+       // Transfer completed
+       event = newProgressEvent(TransferCompletedEvent, tracker.completedBytes, req.ContentLength, 0)
+       publishProgress(listener, event)
+
+       return conn.handleResponse(resp, crc)
+}
+
+func (conn Conn) signURL(method HTTPMethod, bucketName, objectName string, expiration int64, params map[string]interface{}, headers map[string]string) string {
+       akIf := conn.config.GetCredentials()
+       if akIf.GetSecurityToken() != "" {
+               params[HTTPParamSecurityToken] = akIf.GetSecurityToken()
+       }
+
+       m := strings.ToUpper(string(method))
+       req := &http.Request{
+               Method: m,
+               Header: make(http.Header),
+       }
+
+       if conn.config.IsAuthProxy {
+               auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword
+               basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
+               req.Header.Set("Proxy-Authorization", basic)
+       }
+
+       req.Header.Set(HTTPHeaderDate, strconv.FormatInt(expiration, 10))
+       req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent)
+
+       if headers != nil {
+               for k, v := range headers {
+                       req.Header.Set(k, v)
+               }
+       }
+
+       if conn.config.AuthVersion == AuthV2 {
+               params[HTTPParamSignatureVersion] = "OSS2"
+               params[HTTPParamExpiresV2] = strconv.FormatInt(expiration, 10)
+               params[HTTPParamAccessKeyIDV2] = conn.config.AccessKeyID
+               additionalList, _ := conn.getAdditionalHeaderKeys(req)
+               if len(additionalList) > 0 {
+                       params[HTTPParamAdditionalHeadersV2] = strings.Join(additionalList, ";")
+               }
+       }
+
+       subResource := conn.getSubResource(params)
+       canonicalizedResource := conn.getResource(bucketName, objectName, subResource)
+       signedStr := conn.getSignedStr(req, canonicalizedResource, akIf.GetAccessKeySecret())
+
+       if conn.config.AuthVersion == AuthV1 {
+               params[HTTPParamExpires] = strconv.FormatInt(expiration, 10)
+               params[HTTPParamAccessKeyID] = akIf.GetAccessKeyID()
+               params[HTTPParamSignature] = signedStr
+       } else if conn.config.AuthVersion == AuthV2 {
+               params[HTTPParamSignatureV2] = signedStr
+       }
+       urlParams := conn.getURLParams(params)
+       return conn.url.getSignURL(bucketName, objectName, urlParams)
+}
+
+func (conn Conn) signRtmpURL(bucketName, channelName, playlistName string, expiration int64) string {
+       params := map[string]interface{}{}
+       if playlistName != "" {
+               params[HTTPParamPlaylistName] = playlistName
+       }
+       expireStr := strconv.FormatInt(expiration, 10)
+       params[HTTPParamExpires] = expireStr
+
+       akIf := conn.config.GetCredentials()
+       if akIf.GetAccessKeyID() != "" {
+               params[HTTPParamAccessKeyID] = akIf.GetAccessKeyID()
+               if akIf.GetSecurityToken() != "" {
+                       params[HTTPParamSecurityToken] = akIf.GetSecurityToken()
+               }
+               signedStr := conn.getRtmpSignedStr(bucketName, channelName, playlistName, expiration, akIf.GetAccessKeySecret(), params)
+               params[HTTPParamSignature] = signedStr
+       }
+
+       urlParams := conn.getURLParams(params)
+       return conn.url.getSignRtmpURL(bucketName, channelName, urlParams)
+}
+
+// handleBody handles request body
+func (conn Conn) handleBody(req *http.Request, body io.Reader, initCRC uint64,
+       listener ProgressListener, tracker *readerTracker) (*os.File, hash.Hash64) {
+       var file *os.File
+       var crc hash.Hash64
+       reader := body
+       readerLen, err := GetReaderLen(reader)
+       if err == nil {
+               req.ContentLength = readerLen
+       }
+       req.Header.Set(HTTPHeaderContentLength, strconv.FormatInt(req.ContentLength, 10))
+
+       // MD5
+       if body != nil && conn.config.IsEnableMD5 && req.Header.Get(HTTPHeaderContentMD5) == "" {
+               md5 := ""
+               reader, md5, file, _ = calcMD5(body, req.ContentLength, conn.config.MD5Threshold)
+               req.Header.Set(HTTPHeaderContentMD5, md5)
+       }
+
+       // CRC
+       if reader != nil && conn.config.IsEnableCRC {
+               crc = NewCRC(CrcTable(), initCRC)
+               reader = TeeReader(reader, crc, req.ContentLength, listener, tracker)
+       }
+
+       // HTTP body
+       rc, ok := reader.(io.ReadCloser)
+       if !ok && reader != nil {
+               rc = ioutil.NopCloser(reader)
+       }
+
+       if conn.isUploadLimitReq(req) {
+               limitReader := &LimitSpeedReader{
+                       reader:     rc,
+                       ossLimiter: conn.config.UploadLimiter,
+               }
+               req.Body = limitReader
+       } else {
+               req.Body = rc
+       }
+       return file, crc
+}
+
+// isUploadLimitReq: judge limit upload speed or not
+func (conn Conn) isUploadLimitReq(req *http.Request) bool {
+       if conn.config.UploadLimitSpeed == 0 || conn.config.UploadLimiter == nil {
+               return false
+       }
+
+       if req.Method != "GET" && req.Method != "DELETE" && req.Method != "HEAD" {
+               if req.ContentLength > 0 {
+                       return true
+               }
+       }
+       return false
+}
+
+func tryGetFileSize(f *os.File) int64 {
+       fInfo, _ := f.Stat()
+       return fInfo.Size()
+}
+
+// handleResponse handles response
+func (conn Conn) handleResponse(resp *http.Response, crc hash.Hash64) (*Response, error) {
+       var cliCRC uint64
+       var srvCRC uint64
+
+       statusCode := resp.StatusCode
+       if statusCode >= 400 && statusCode <= 505 {
+               // 4xx and 5xx indicate that the operation has error occurred
+               var respBody []byte
+               respBody, err := readResponseBody(resp)
+               if err != nil {
+                       return nil, err
+               }
+
+               if len(respBody) == 0 {
+                       err = ServiceError{
+                               StatusCode: statusCode,
+                               RequestID:  resp.Header.Get(HTTPHeaderOssRequestID),
+                       }
+               } else {
+                       // Response contains storage service error object, unmarshal
+                       srvErr, errIn := serviceErrFromXML(respBody, resp.StatusCode,
+                               resp.Header.Get(HTTPHeaderOssRequestID))
+                       if errIn != nil { // error unmarshaling the error response
+                               err = fmt.Errorf("oss: service returned invalid response body, status = %s, RequestId = %s", resp.Status, resp.Header.Get(HTTPHeaderOssRequestID))
+                       } else {
+                               err = srvErr
+                       }
+               }
+
+               return &Response{
+                       StatusCode: resp.StatusCode,
+                       Headers:    resp.Header,
+                       Body:       ioutil.NopCloser(bytes.NewReader(respBody)), // restore the body
+               }, err
+       } else if statusCode >= 300 && statusCode <= 307 {
+               // OSS use 3xx, but response has no body
+               err := fmt.Errorf("oss: service returned %d,%s", resp.StatusCode, resp.Status)
+               return &Response{
+                       StatusCode: resp.StatusCode,
+                       Headers:    resp.Header,
+                       Body:       resp.Body,
+               }, err
+       }
+
+       if conn.config.IsEnableCRC && crc != nil {
+               cliCRC = crc.Sum64()
+       }
+       srvCRC, _ = strconv.ParseUint(resp.Header.Get(HTTPHeaderOssCRC64), 10, 64)
+
+       // 2xx, successful
+       return &Response{
+               StatusCode: resp.StatusCode,
+               Headers:    resp.Header,
+               Body:       resp.Body,
+               ClientCRC:  cliCRC,
+               ServerCRC:  srvCRC,
+       }, nil
+}
+
+// LoggerHTTPReq Print the header information of the http request
+func (conn Conn) LoggerHTTPReq(req *http.Request) {
+       var logBuffer bytes.Buffer
+       logBuffer.WriteString(fmt.Sprintf("[Req:%p]Method:%s\t", req, req.Method))
+       logBuffer.WriteString(fmt.Sprintf("Host:%s\t", req.URL.Host))
+       logBuffer.WriteString(fmt.Sprintf("Path:%s\t", req.URL.Path))
+       logBuffer.WriteString(fmt.Sprintf("Query:%s\t", req.URL.RawQuery))
+       logBuffer.WriteString(fmt.Sprintf("Header info:"))
+
+       for k, v := range req.Header {
+               var valueBuffer bytes.Buffer
+               for j := 0; j < len(v); j++ {
+                       if j > 0 {
+                               valueBuffer.WriteString(" ")
+                       }
+                       valueBuffer.WriteString(v[j])
+               }
+               logBuffer.WriteString(fmt.Sprintf("\t%s:%s", k, valueBuffer.String()))
+       }
+       conn.config.WriteLog(Debug, "%s\n", logBuffer.String())
+}
+
+// LoggerHTTPResp Print Response to http request
+func (conn Conn) LoggerHTTPResp(req *http.Request, resp *http.Response) {
+       var logBuffer bytes.Buffer
+       logBuffer.WriteString(fmt.Sprintf("[Resp:%p]StatusCode:%d\t", req, resp.StatusCode))
+       logBuffer.WriteString(fmt.Sprintf("Header info:"))
+       for k, v := range resp.Header {
+               var valueBuffer bytes.Buffer
+               for j := 0; j < len(v); j++ {
+                       if j > 0 {
+                               valueBuffer.WriteString(" ")
+                       }
+                       valueBuffer.WriteString(v[j])
+               }
+               logBuffer.WriteString(fmt.Sprintf("\t%s:%s", k, valueBuffer.String()))
+       }
+       conn.config.WriteLog(Debug, "%s\n", logBuffer.String())
+}
+
+func calcMD5(body io.Reader, contentLen, md5Threshold int64) (reader io.Reader, b64 string, tempFile *os.File, err error) {
+       if contentLen == 0 || contentLen > md5Threshold {
+               // Huge body, use temporary file
+               tempFile, err = ioutil.TempFile(os.TempDir(), TempFilePrefix)
+               if tempFile != nil {
+                       io.Copy(tempFile, body)
+                       tempFile.Seek(0, os.SEEK_SET)
+                       md5 := md5.New()
+                       io.Copy(md5, tempFile)
+                       sum := md5.Sum(nil)
+                       b64 = base64.StdEncoding.EncodeToString(sum[:])
+                       tempFile.Seek(0, os.SEEK_SET)
+                       reader = tempFile
+               }
+       } else {
+               // Small body, use memory
+               buf, _ := ioutil.ReadAll(body)
+               sum := md5.Sum(buf)
+               b64 = base64.StdEncoding.EncodeToString(sum[:])
+               reader = bytes.NewReader(buf)
+       }
+       return
+}
+
+func readResponseBody(resp *http.Response) ([]byte, error) {
+       defer resp.Body.Close()
+       out, err := ioutil.ReadAll(resp.Body)
+       if err == io.EOF {
+               err = nil
+       }
+       return out, err
+}
+
+func serviceErrFromXML(body []byte, statusCode int, requestID string) (ServiceError, error) {
+       var storageErr ServiceError
+
+       if err := xml.Unmarshal(body, &storageErr); err != nil {
+               return storageErr, err
+       }
+
+       storageErr.StatusCode = statusCode
+       storageErr.RequestID = requestID
+       storageErr.RawMessage = string(body)
+       return storageErr, nil
+}
+
+func xmlUnmarshal(body io.Reader, v interface{}) error {
+       data, err := ioutil.ReadAll(body)
+       if err != nil {
+               return err
+       }
+       return xml.Unmarshal(data, v)
+}
+
+func jsonUnmarshal(body io.Reader, v interface{}) error {
+       data, err := ioutil.ReadAll(body)
+       if err != nil {
+               return err
+       }
+       return json.Unmarshal(data, v)
+}
+
+// timeoutConn handles HTTP timeout
+type timeoutConn struct {
+       conn        net.Conn
+       timeout     time.Duration
+       longTimeout time.Duration
+}
+
+func newTimeoutConn(conn net.Conn, timeout time.Duration, longTimeout time.Duration) *timeoutConn {
+       conn.SetReadDeadline(time.Now().Add(longTimeout))
+       return &timeoutConn{
+               conn:        conn,
+               timeout:     timeout,
+               longTimeout: longTimeout,
+       }
+}
+
+func (c *timeoutConn) Read(b []byte) (n int, err error) {
+       c.SetReadDeadline(time.Now().Add(c.timeout))
+       n, err = c.conn.Read(b)
+       c.SetReadDeadline(time.Now().Add(c.longTimeout))
+       return n, err
+}
+
+func (c *timeoutConn) Write(b []byte) (n int, err error) {
+       c.SetWriteDeadline(time.Now().Add(c.timeout))
+       n, err = c.conn.Write(b)
+       c.SetReadDeadline(time.Now().Add(c.longTimeout))
+       return n, err
+}
+
+func (c *timeoutConn) Close() error {
+       return c.conn.Close()
+}
+
+func (c *timeoutConn) LocalAddr() net.Addr {
+       return c.conn.LocalAddr()
+}
+
+func (c *timeoutConn) RemoteAddr() net.Addr {
+       return c.conn.RemoteAddr()
+}
+
+func (c *timeoutConn) SetDeadline(t time.Time) error {
+       return c.conn.SetDeadline(t)
+}
+
+func (c *timeoutConn) SetReadDeadline(t time.Time) error {
+       return c.conn.SetReadDeadline(t)
+}
+
+func (c *timeoutConn) SetWriteDeadline(t time.Time) error {
+       return c.conn.SetWriteDeadline(t)
+}
+
+// UrlMaker builds URL and resource
+const (
+       urlTypeCname  = 1
+       urlTypeIP     = 2
+       urlTypeAliyun = 3
+)
+
+type urlMaker struct {
+       Scheme  string // HTTP or HTTPS
+       NetLoc  string // Host or IP
+       Type    int    // 1 CNAME, 2 IP, 3 ALIYUN
+       IsProxy bool   // Proxy
+}
+
+// Init parses endpoint
+func (um *urlMaker) Init(endpoint string, isCname bool, isProxy bool) error {
+       if strings.HasPrefix(endpoint, "http://") {
+               um.Scheme = "http"
+               um.NetLoc = endpoint[len("http://"):]
+       } else if strings.HasPrefix(endpoint, "https://") {
+               um.Scheme = "https"
+               um.NetLoc = endpoint[len("https://"):]
+       } else {
+               um.Scheme = "http"
+               um.NetLoc = endpoint
+       }
+
+       //use url.Parse() to get real host
+       strUrl := um.Scheme + "://" + um.NetLoc
+       url, err := url.Parse(strUrl)
+       if err != nil {
+               return err
+       }
+
+       um.NetLoc = url.Host
+       host, _, err := net.SplitHostPort(um.NetLoc)
+       if err != nil {
+               host = um.NetLoc
+               if host[0] == '[' && host[len(host)-1] == ']' {
+                       host = host[1 : len(host)-1]
+               }
+       }
+
+       ip := net.ParseIP(host)
+       if ip != nil {
+               um.Type = urlTypeIP
+       } else if isCname {
+               um.Type = urlTypeCname
+       } else {
+               um.Type = urlTypeAliyun
+       }
+       um.IsProxy = isProxy
+
+       return nil
+}
+
+// getURL gets URL
+func (um urlMaker) getURL(bucket, object, params string) *url.URL {
+       host, path := um.buildURL(bucket, object)
+       addr := ""
+       if params == "" {
+               addr = fmt.Sprintf("%s://%s%s", um.Scheme, host, path)
+       } else {
+               addr = fmt.Sprintf("%s://%s%s?%s", um.Scheme, host, path, params)
+       }
+       uri, _ := url.ParseRequestURI(addr)
+       return uri
+}
+
+// getSignURL gets sign URL
+func (um urlMaker) getSignURL(bucket, object, params string) string {
+       host, path := um.buildURL(bucket, object)
+       return fmt.Sprintf("%s://%s%s?%s", um.Scheme, host, path, params)
+}
+
+// getSignRtmpURL Build Sign Rtmp URL
+func (um urlMaker) getSignRtmpURL(bucket, channelName, params string) string {
+       host, path := um.buildURL(bucket, "live")
+
+       channelName = url.QueryEscape(channelName)
+       channelName = strings.Replace(channelName, "+", "%20", -1)
+
+       return fmt.Sprintf("rtmp://%s%s/%s?%s", host, path, channelName, params)
+}
+
+// buildURL builds URL
+func (um urlMaker) buildURL(bucket, object string) (string, string) {
+       var host = ""
+       var path = ""
+
+       object = url.QueryEscape(object)
+       object = strings.Replace(object, "+", "%20", -1)
+
+       if um.Type == urlTypeCname {
+               host = um.NetLoc
+               path = "/" + object
+       } else if um.Type == urlTypeIP {
+               if bucket == "" {
+                       host = um.NetLoc
+                       path = "/"
+               } else {
+                       host = um.NetLoc
+                       path = fmt.Sprintf("/%s/%s", bucket, object)
+               }
+       } else {
+               if bucket == "" {
+                       host = um.NetLoc
+                       path = "/"
+               } else {
+                       host = bucket + "." + um.NetLoc
+                       path = "/" + object
+               }
+       }
+
+       return host, path
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn_test.go
new file mode 100644 (file)
index 0000000..be57867
--- /dev/null
@@ -0,0 +1,192 @@
+package oss
+
+import (
+       "net/http"
+       "os"
+       "strings"
+       "time"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssConnSuite struct{}
+
+var _ = Suite(&OssConnSuite{})
+
+func (s *OssConnSuite) TestURLMarker(c *C) {
+       um := urlMaker{}
+       um.Init("docs.github.com", true, false)
+       c.Assert(um.Type, Equals, urlTypeCname)
+       c.Assert(um.Scheme, Equals, "http")
+       c.Assert(um.NetLoc, Equals, "docs.github.com")
+
+       c.Assert(um.getURL("bucket", "object", "params").String(), Equals, "http://docs.github.com/object?params")
+       c.Assert(um.getURL("bucket", "object", "").String(), Equals, "http://docs.github.com/object")
+       c.Assert(um.getURL("", "object", "").String(), Equals, "http://docs.github.com/object")
+
+       var conn Conn
+       conn.config = getDefaultOssConfig()
+       conn.config.AuthVersion = AuthV1
+       c.Assert(conn.getResource("bucket", "object", "subres"), Equals, "/bucket/object?subres")
+       c.Assert(conn.getResource("bucket", "object", ""), Equals, "/bucket/object")
+       c.Assert(conn.getResource("", "object", ""), Equals, "/")
+
+       um.Init("https://docs.github.com", true, false)
+       c.Assert(um.Type, Equals, urlTypeCname)
+       c.Assert(um.Scheme, Equals, "https")
+       c.Assert(um.NetLoc, Equals, "docs.github.com")
+
+       um.Init("http://docs.github.com", true, false)
+       c.Assert(um.Type, Equals, urlTypeCname)
+       c.Assert(um.Scheme, Equals, "http")
+       c.Assert(um.NetLoc, Equals, "docs.github.com")
+
+       um.Init("docs.github.com:8080", false, true)
+       c.Assert(um.Type, Equals, urlTypeAliyun)
+       c.Assert(um.Scheme, Equals, "http")
+       c.Assert(um.NetLoc, Equals, "docs.github.com:8080")
+
+       c.Assert(um.getURL("bucket", "object", "params").String(), Equals, "http://bucket.docs.github.com:8080/object?params")
+       c.Assert(um.getURL("bucket", "object", "").String(), Equals, "http://bucket.docs.github.com:8080/object")
+       c.Assert(um.getURL("", "object", "").String(), Equals, "http://docs.github.com:8080/")
+       c.Assert(conn.getResource("bucket", "object", "subres"), Equals, "/bucket/object?subres")
+       c.Assert(conn.getResource("bucket", "object", ""), Equals, "/bucket/object")
+       c.Assert(conn.getResource("", "object", ""), Equals, "/")
+
+       um.Init("https://docs.github.com:8080", false, true)
+       c.Assert(um.Type, Equals, urlTypeAliyun)
+       c.Assert(um.Scheme, Equals, "https")
+       c.Assert(um.NetLoc, Equals, "docs.github.com:8080")
+
+       um.Init("127.0.0.1", false, true)
+       c.Assert(um.Type, Equals, urlTypeIP)
+       c.Assert(um.Scheme, Equals, "http")
+       c.Assert(um.NetLoc, Equals, "127.0.0.1")
+
+       um.Init("http://127.0.0.1", false, false)
+       c.Assert(um.Type, Equals, urlTypeIP)
+       c.Assert(um.Scheme, Equals, "http")
+       c.Assert(um.NetLoc, Equals, "127.0.0.1")
+       c.Assert(um.getURL("bucket", "object", "params").String(), Equals, "http://127.0.0.1/bucket/object?params")
+       c.Assert(um.getURL("", "object", "params").String(), Equals, "http://127.0.0.1/?params")
+
+       um.Init("https://127.0.0.1:8080", false, false)
+       c.Assert(um.Type, Equals, urlTypeIP)
+       c.Assert(um.Scheme, Equals, "https")
+       c.Assert(um.NetLoc, Equals, "127.0.0.1:8080")
+
+       um.Init("http://[2401:b180::dc]", false, false)
+       c.Assert(um.Type, Equals, urlTypeIP)
+       c.Assert(um.Scheme, Equals, "http")
+       c.Assert(um.NetLoc, Equals, "[2401:b180::dc]")
+
+       um.Init("https://[2401:b180::dc]:8080", false, false)
+       c.Assert(um.Type, Equals, urlTypeIP)
+       c.Assert(um.Scheme, Equals, "https")
+       c.Assert(um.NetLoc, Equals, "[2401:b180::dc]:8080")
+}
+
+func (s *OssConnSuite) TestAuth(c *C) {
+       endpoint := "https://github.com/"
+       cfg := getDefaultOssConfig()
+       cfg.AuthVersion = AuthV1
+       um := urlMaker{}
+       um.Init(endpoint, false, false)
+       conn := Conn{cfg, &um, nil}
+       uri := um.getURL("bucket", "object", "")
+       req := &http.Request{
+               Method:     "PUT",
+               URL:        uri,
+               Proto:      "HTTP/1.1",
+               ProtoMajor: 1,
+               ProtoMinor: 1,
+               Header:     make(http.Header),
+               Host:       uri.Host,
+       }
+
+       req.Header.Set("Content-Type", "text/html")
+       req.Header.Set("Date", "Thu, 17 Nov 2005 18:49:58 GMT")
+       req.Header.Set("Host", endpoint)
+       req.Header.Set("X-OSS-Meta-Your", "your")
+       req.Header.Set("X-OSS-Meta-Author", "foo@bar.com")
+       req.Header.Set("X-OSS-Magic", "abracadabra")
+       req.Header.Set("Content-Md5", "ODBGOERFMDMzQTczRUY3NUE3NzA5QzdFNUYzMDQxNEM=")
+
+       conn.signHeader(req, conn.getResource("bucket", "object", ""))
+       testLogger.Println("AUTHORIZATION:", req.Header.Get(HTTPHeaderAuthorization))
+}
+
+func (s *OssConnSuite) TestConnToolFunc(c *C) {
+       err := CheckRespCode(202, []int{})
+       c.Assert(err, NotNil)
+
+       err = CheckRespCode(202, []int{404})
+       c.Assert(err, NotNil)
+
+       err = CheckRespCode(202, []int{202, 404})
+       c.Assert(err, IsNil)
+
+       srvErr, err := serviceErrFromXML([]byte(""), 312, "")
+       c.Assert(err, NotNil)
+       c.Assert(srvErr.StatusCode, Equals, 0)
+
+       srvErr, err = serviceErrFromXML([]byte("ABC"), 312, "")
+       c.Assert(err, NotNil)
+       c.Assert(srvErr.StatusCode, Equals, 0)
+
+       srvErr, err = serviceErrFromXML([]byte("<Error></Error>"), 312, "")
+       c.Assert(err, IsNil)
+       c.Assert(srvErr.StatusCode, Equals, 312)
+
+       unexpect := UnexpectedStatusCodeError{[]int{200}, 202}
+       c.Assert(len(unexpect.Error()) > 0, Equals, true)
+       c.Assert(unexpect.Got(), Equals, 202)
+
+       fd, err := os.Open("../sample/BingWallpaper-2015-11-07.jpg")
+       c.Assert(err, IsNil)
+       fd.Close()
+       var out ProcessObjectResult
+       err = jsonUnmarshal(fd, &out)
+       c.Assert(err, NotNil)
+}
+
+func (s *OssConnSuite) TestSignRtmpURL(c *C) {
+       cfg := getDefaultOssConfig()
+
+       um := urlMaker{}
+       um.Init(endpoint, false, false)
+       conn := Conn{cfg, &um, nil}
+
+       //Anonymous
+       channelName := "test-sign-rtmp-url"
+       playlistName := "playlist.m3u8"
+       expiration := time.Now().Unix() + 3600
+       signedRtmpURL := conn.signRtmpURL(bucketName, channelName, playlistName, expiration)
+       playURL := getPublishURL(bucketName, channelName)
+       hasPrefix := strings.HasPrefix(signedRtmpURL, playURL)
+       c.Assert(hasPrefix, Equals, true)
+
+       //empty playlist name
+       playlistName = ""
+       signedRtmpURL = conn.signRtmpURL(bucketName, channelName, playlistName, expiration)
+       playURL = getPublishURL(bucketName, channelName)
+       hasPrefix = strings.HasPrefix(signedRtmpURL, playURL)
+       c.Assert(hasPrefix, Equals, true)
+}
+
+func (s *OssConnSuite) TestGetRtmpSignedStr(c *C) {
+       cfg := getDefaultOssConfig()
+       um := urlMaker{}
+       um.Init(endpoint, false, false)
+       conn := Conn{cfg, &um, nil}
+
+       akIf := conn.config.GetCredentials()
+
+       //Anonymous
+       channelName := "test-get-rtmp-signed-str"
+       playlistName := "playlist.m3u8"
+       expiration := time.Now().Unix() + 3600
+       params := map[string]interface{}{}
+       signedStr := conn.getRtmpSignedStr(bucketName, channelName, playlistName, expiration, akIf.GetAccessKeySecret(), params)
+       c.Assert(signedStr, Equals, "")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/const.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/const.go
new file mode 100644 (file)
index 0000000..cf3329f
--- /dev/null
@@ -0,0 +1,247 @@
+package oss
+
+import "os"
+
+// ACLType bucket/object ACL
+type ACLType string
+
+const (
+       // ACLPrivate definition : private read and write
+       ACLPrivate ACLType = "private"
+
+       // ACLPublicRead definition : public read and private write
+       ACLPublicRead ACLType = "public-read"
+
+       // ACLPublicReadWrite definition : public read and public write
+       ACLPublicReadWrite ACLType = "public-read-write"
+
+       // ACLDefault Object. It's only applicable for object.
+       ACLDefault ACLType = "default"
+)
+
+// bucket versioning status
+type VersioningStatus string
+
+const (
+       // Versioning Status definition: Enabled
+       VersionEnabled VersioningStatus = "Enabled"
+
+       // Versioning Status definition: Suspended
+       VersionSuspended VersioningStatus = "Suspended"
+)
+
+// MetadataDirectiveType specifying whether use the metadata of source object when copying object.
+type MetadataDirectiveType string
+
+const (
+       // MetaCopy the target object's metadata is copied from the source one
+       MetaCopy MetadataDirectiveType = "COPY"
+
+       // MetaReplace the target object's metadata is created as part of the copy request (not same as the source one)
+       MetaReplace MetadataDirectiveType = "REPLACE"
+)
+
+// TaggingDirectiveType specifying whether use the tagging of source object when copying object.
+type TaggingDirectiveType string
+
+const (
+       // TaggingCopy the target object's tagging is copied from the source one
+       TaggingCopy TaggingDirectiveType = "COPY"
+
+       // TaggingReplace the target object's tagging is created as part of the copy request (not same as the source one)
+       TaggingReplace TaggingDirectiveType = "REPLACE"
+)
+
+// AlgorithmType specifying the server side encryption algorithm name
+type AlgorithmType string
+
+const (
+       KMSAlgorithm AlgorithmType = "KMS"
+       AESAlgorithm AlgorithmType = "AES256"
+       SM4Algorithm AlgorithmType = "SM4"
+)
+
+// StorageClassType bucket storage type
+type StorageClassType string
+
+const (
+       // StorageStandard standard
+       StorageStandard StorageClassType = "Standard"
+
+       // StorageIA infrequent access
+       StorageIA StorageClassType = "IA"
+
+       // StorageArchive archive
+       StorageArchive StorageClassType = "Archive"
+
+       // StorageColdArchive cold archive
+       StorageColdArchive StorageClassType = "ColdArchive"
+)
+
+//RedundancyType bucket data Redundancy type
+type DataRedundancyType string
+
+const (
+       // RedundancyLRS Local redundancy, default value
+       RedundancyLRS DataRedundancyType = "LRS"
+
+       // RedundancyZRS Same city redundancy
+       RedundancyZRS DataRedundancyType = "ZRS"
+)
+
+// PayerType the type of request payer
+type PayerType string
+
+const (
+       // Requester the requester who send the request
+       Requester PayerType = "Requester"
+
+       // BucketOwner the requester who send the request
+       BucketOwner PayerType = "BucketOwner"
+)
+
+//RestoreMode the restore mode for coldArchive object
+type RestoreMode string
+
+const (
+       //RestoreExpedited object will be restored in 1 hour
+       RestoreExpedited RestoreMode = "Expedited"
+
+       //RestoreStandard object will be restored in 2-5 hours
+       RestoreStandard RestoreMode = "Standard"
+
+       //RestoreBulk object will be restored in 5-10 hours
+       RestoreBulk RestoreMode = "Bulk"
+)
+
+// HTTPMethod HTTP request method
+type HTTPMethod string
+
+const (
+       // HTTPGet HTTP GET
+       HTTPGet HTTPMethod = "GET"
+
+       // HTTPPut HTTP PUT
+       HTTPPut HTTPMethod = "PUT"
+
+       // HTTPHead HTTP HEAD
+       HTTPHead HTTPMethod = "HEAD"
+
+       // HTTPPost HTTP POST
+       HTTPPost HTTPMethod = "POST"
+
+       // HTTPDelete HTTP DELETE
+       HTTPDelete HTTPMethod = "DELETE"
+)
+
+// HTTP headers
+const (
+       HTTPHeaderAcceptEncoding     string = "Accept-Encoding"
+       HTTPHeaderAuthorization             = "Authorization"
+       HTTPHeaderCacheControl              = "Cache-Control"
+       HTTPHeaderContentDisposition        = "Content-Disposition"
+       HTTPHeaderContentEncoding           = "Content-Encoding"
+       HTTPHeaderContentLength             = "Content-Length"
+       HTTPHeaderContentMD5                = "Content-MD5"
+       HTTPHeaderContentType               = "Content-Type"
+       HTTPHeaderContentLanguage           = "Content-Language"
+       HTTPHeaderDate                      = "Date"
+       HTTPHeaderEtag                      = "ETag"
+       HTTPHeaderExpires                   = "Expires"
+       HTTPHeaderHost                      = "Host"
+       HTTPHeaderLastModified              = "Last-Modified"
+       HTTPHeaderRange                     = "Range"
+       HTTPHeaderLocation                  = "Location"
+       HTTPHeaderOrigin                    = "Origin"
+       HTTPHeaderServer                    = "Server"
+       HTTPHeaderUserAgent                 = "User-Agent"
+       HTTPHeaderIfModifiedSince           = "If-Modified-Since"
+       HTTPHeaderIfUnmodifiedSince         = "If-Unmodified-Since"
+       HTTPHeaderIfMatch                   = "If-Match"
+       HTTPHeaderIfNoneMatch               = "If-None-Match"
+       HTTPHeaderACReqMethod               = "Access-Control-Request-Method"
+       HTTPHeaderACReqHeaders              = "Access-Control-Request-Headers"
+
+       HTTPHeaderOssACL                         = "X-Oss-Acl"
+       HTTPHeaderOssMetaPrefix                  = "X-Oss-Meta-"
+       HTTPHeaderOssObjectACL                   = "X-Oss-Object-Acl"
+       HTTPHeaderOssSecurityToken               = "X-Oss-Security-Token"
+       HTTPHeaderOssServerSideEncryption        = "X-Oss-Server-Side-Encryption"
+       HTTPHeaderOssServerSideEncryptionKeyID   = "X-Oss-Server-Side-Encryption-Key-Id"
+       HTTPHeaderOssServerSideDataEncryption    = "X-Oss-Server-Side-Data-Encryption"
+       HTTPHeaderSSECAlgorithm                  = "X-Oss-Server-Side-Encryption-Customer-Algorithm"
+       HTTPHeaderSSECKey                        = "X-Oss-Server-Side-Encryption-Customer-Key"
+       HTTPHeaderSSECKeyMd5                     = "X-Oss-Server-Side-Encryption-Customer-Key-MD5"
+       HTTPHeaderOssCopySource                  = "X-Oss-Copy-Source"
+       HTTPHeaderOssCopySourceRange             = "X-Oss-Copy-Source-Range"
+       HTTPHeaderOssCopySourceIfMatch           = "X-Oss-Copy-Source-If-Match"
+       HTTPHeaderOssCopySourceIfNoneMatch       = "X-Oss-Copy-Source-If-None-Match"
+       HTTPHeaderOssCopySourceIfModifiedSince   = "X-Oss-Copy-Source-If-Modified-Since"
+       HTTPHeaderOssCopySourceIfUnmodifiedSince = "X-Oss-Copy-Source-If-Unmodified-Since"
+       HTTPHeaderOssMetadataDirective           = "X-Oss-Metadata-Directive"
+       HTTPHeaderOssNextAppendPosition          = "X-Oss-Next-Append-Position"
+       HTTPHeaderOssRequestID                   = "X-Oss-Request-Id"
+       HTTPHeaderOssCRC64                       = "X-Oss-Hash-Crc64ecma"
+       HTTPHeaderOssSymlinkTarget               = "X-Oss-Symlink-Target"
+       HTTPHeaderOssStorageClass                = "X-Oss-Storage-Class"
+       HTTPHeaderOssCallback                    = "X-Oss-Callback"
+       HTTPHeaderOssCallbackVar                 = "X-Oss-Callback-Var"
+       HTTPHeaderOssRequester                   = "X-Oss-Request-Payer"
+       HTTPHeaderOssTagging                     = "X-Oss-Tagging"
+       HTTPHeaderOssTaggingDirective            = "X-Oss-Tagging-Directive"
+       HTTPHeaderOssTrafficLimit                = "X-Oss-Traffic-Limit"
+       HTTPHeaderOssForbidOverWrite             = "X-Oss-Forbid-Overwrite"
+       HTTPHeaderOssRangeBehavior               = "X-Oss-Range-Behavior"
+       HTTPHeaderOssTaskID                      = "X-Oss-Task-Id"
+)
+
+// HTTP Param
+const (
+       HTTPParamExpires       = "Expires"
+       HTTPParamAccessKeyID   = "OSSAccessKeyId"
+       HTTPParamSignature     = "Signature"
+       HTTPParamSecurityToken = "security-token"
+       HTTPParamPlaylistName  = "playlistName"
+
+       HTTPParamSignatureVersion    = "x-oss-signature-version"
+       HTTPParamExpiresV2           = "x-oss-expires"
+       HTTPParamAccessKeyIDV2       = "x-oss-access-key-id"
+       HTTPParamSignatureV2         = "x-oss-signature"
+       HTTPParamAdditionalHeadersV2 = "x-oss-additional-headers"
+)
+
+// Other constants
+const (
+       MaxPartSize = 5 * 1024 * 1024 * 1024 // Max part size, 5GB
+       MinPartSize = 100 * 1024             // Min part size, 100KB
+
+       FilePermMode = os.FileMode(0664) // Default file permission
+
+       TempFilePrefix = "oss-go-temp-" // Temp file prefix
+       TempFileSuffix = ".temp"        // Temp file suffix
+
+       CheckpointFileSuffix = ".cp" // Checkpoint file suffix
+
+       NullVersion = "null"
+
+       Version = "v2.1.6" // Go SDK version
+)
+
+// FrameType
+const (
+       DataFrameType        = 8388609
+       ContinuousFrameType  = 8388612
+       EndFrameType         = 8388613
+       MetaEndFrameCSVType  = 8388614
+       MetaEndFrameJSONType = 8388615
+)
+
+// AuthVersion the version of auth
+type AuthVersionType string
+
+const (
+       // AuthV1 v1
+       AuthV1 AuthVersionType = "v1"
+       // AuthV2 v2
+       AuthV2 AuthVersionType = "v2"
+)
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc.go
new file mode 100644 (file)
index 0000000..c96694f
--- /dev/null
@@ -0,0 +1,123 @@
+package oss
+
+import (
+       "hash"
+       "hash/crc64"
+)
+
+// digest represents the partial evaluation of a checksum.
+type digest struct {
+       crc uint64
+       tab *crc64.Table
+}
+
+// NewCRC creates a new hash.Hash64 computing the CRC64 checksum
+// using the polynomial represented by the Table.
+func NewCRC(tab *crc64.Table, init uint64) hash.Hash64 { return &digest{init, tab} }
+
+// Size returns the number of bytes sum will return.
+func (d *digest) Size() int { return crc64.Size }
+
+// BlockSize returns the hash's underlying block size.
+// The Write method must be able to accept any amount
+// of data, but it may operate more efficiently if all writes
+// are a multiple of the block size.
+func (d *digest) BlockSize() int { return 1 }
+
+// Reset resets the hash to its initial state.
+func (d *digest) Reset() { d.crc = 0 }
+
+// Write (via the embedded io.Writer interface) adds more data to the running hash.
+// It never returns an error.
+func (d *digest) Write(p []byte) (n int, err error) {
+       d.crc = crc64.Update(d.crc, d.tab, p)
+       return len(p), nil
+}
+
+// Sum64 returns CRC64 value.
+func (d *digest) Sum64() uint64 { return d.crc }
+
+// Sum returns hash value.
+func (d *digest) Sum(in []byte) []byte {
+       s := d.Sum64()
+       return append(in, byte(s>>56), byte(s>>48), byte(s>>40), byte(s>>32), byte(s>>24), byte(s>>16), byte(s>>8), byte(s))
+}
+
+// gf2Dim dimension of GF(2) vectors (length of CRC)
+const gf2Dim int = 64
+
+func gf2MatrixTimes(mat []uint64, vec uint64) uint64 {
+       var sum uint64
+       for i := 0; vec != 0; i++ {
+               if vec&1 != 0 {
+                       sum ^= mat[i]
+               }
+
+               vec >>= 1
+       }
+       return sum
+}
+
+func gf2MatrixSquare(square []uint64, mat []uint64) {
+       for n := 0; n < gf2Dim; n++ {
+               square[n] = gf2MatrixTimes(mat, mat[n])
+       }
+}
+
+// CRC64Combine combines CRC64
+func CRC64Combine(crc1 uint64, crc2 uint64, len2 uint64) uint64 {
+       var even [gf2Dim]uint64 // Even-power-of-two zeros operator
+       var odd [gf2Dim]uint64  // Odd-power-of-two zeros operator
+
+       // Degenerate case
+       if len2 == 0 {
+               return crc1
+       }
+
+       // Put operator for one zero bit in odd
+       odd[0] = crc64.ECMA // CRC64 polynomial
+       var row uint64 = 1
+       for n := 1; n < gf2Dim; n++ {
+               odd[n] = row
+               row <<= 1
+       }
+
+       // Put operator for two zero bits in even
+       gf2MatrixSquare(even[:], odd[:])
+
+       // Put operator for four zero bits in odd
+       gf2MatrixSquare(odd[:], even[:])
+
+       // Apply len2 zeros to crc1, first square will put the operator for one zero byte, eight zero bits, in even
+       for {
+               // Apply zeros operator for this bit of len2
+               gf2MatrixSquare(even[:], odd[:])
+
+               if len2&1 != 0 {
+                       crc1 = gf2MatrixTimes(even[:], crc1)
+               }
+
+               len2 >>= 1
+
+               // If no more bits set, then done
+               if len2 == 0 {
+                       break
+               }
+
+               // Another iteration of the loop with odd and even swapped
+               gf2MatrixSquare(odd[:], even[:])
+               if len2&1 != 0 {
+                       crc1 = gf2MatrixTimes(odd[:], crc1)
+               }
+               len2 >>= 1
+
+               // If no more bits set, then done
+               if len2 == 0 {
+                       break
+               }
+       }
+
+       // Return combined CRC
+       crc1 ^= crc2
+       return crc1
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc_test.go
new file mode 100644 (file)
index 0000000..1254891
--- /dev/null
@@ -0,0 +1,492 @@
+package oss
+
+import (
+       "crypto/md5"
+       "encoding/base64"
+       "hash/crc64"
+       "io"
+       "io/ioutil"
+       "math/rand"
+       "os"
+       "strings"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssCrcSuite struct {
+       client *Client
+       bucket *Bucket
+}
+
+var _ = Suite(&OssCrcSuite{})
+
+// SetUpSuite runs once when the suite starts running
+func (s *OssCrcSuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       s.client = client
+
+       s.client.CreateBucket(bucketName)
+
+       bucket, err := s.client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       s.bucket = bucket
+
+       testLogger.Println("test crc started")
+}
+
+// TearDownSuite runs before each test or benchmark starts running
+func (s *OssCrcSuite) TearDownSuite(c *C) {
+       // Delete part
+       keyMarker := KeyMarker("")
+       uploadIDMarker := UploadIDMarker("")
+       for {
+               lmur, err := s.bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+               c.Assert(err, IsNil)
+               for _, upload := range lmur.Uploads {
+                       var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName,
+                               Key: upload.Key, UploadID: upload.UploadID}
+                       err = s.bucket.AbortMultipartUpload(imur)
+                       c.Assert(err, IsNil)
+               }
+               keyMarker = KeyMarker(lmur.NextKeyMarker)
+               uploadIDMarker = UploadIDMarker(lmur.NextUploadIDMarker)
+               if !lmur.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete objects
+       marker := Marker("")
+       for {
+               lor, err := s.bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = s.bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete bucket
+       err := s.client.DeleteBucket(s.bucket.BucketName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("test crc completed")
+}
+
+// SetUpTest runs after each test or benchmark runs
+func (s *OssCrcSuite) SetUpTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+}
+
+// TearDownTest runs once after all tests or benchmarks have finished running
+func (s *OssCrcSuite) TearDownTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+}
+
+// TestCRCGolden tests OSS's CRC64
+func (s *OssCrcSuite) TestCRCGolden(c *C) {
+       type crcTest struct {
+               out uint64
+               in  string
+       }
+
+       var crcGolden = []crcTest{
+               {0x0, ""},
+               {0x3420000000000000, "a"},
+               {0x36c4200000000000, "ab"},
+               {0x3776c42000000000, "abc"},
+               {0x336776c420000000, "abcd"},
+               {0x32d36776c4200000, "abcde"},
+               {0x3002d36776c42000, "abcdef"},
+               {0x31b002d36776c420, "abcdefg"},
+               {0xe21b002d36776c4, "abcdefgh"},
+               {0x8b6e21b002d36776, "abcdefghi"},
+               {0x7f5b6e21b002d367, "abcdefghij"},
+               {0x8ec0e7c835bf9cdf, "Discard medicine more than two years old."},
+               {0xc7db1759e2be5ab4, "He who has a shady past knows that nice guys finish last."},
+               {0xfbf9d9603a6fa020, "I wouldn't marry him with a ten foot pole."},
+               {0xeafc4211a6daa0ef, "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"},
+               {0x3e05b21c7a4dc4da, "The days of the digital watch are numbered.  -Tom Stoppard"},
+               {0x5255866ad6ef28a6, "Nepal premier won't resign."},
+               {0x8a79895be1e9c361, "For every action there is an equal and opposite government program."},
+               {0x8878963a649d4916, "His money is twice tainted: 'taint yours and 'taint mine."},
+               {0xa7b9d53ea87eb82f, "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"},
+               {0xdb6805c0966a2f9c, "It's a tiny change to the code and not completely disgusting. - Bob Manchek"},
+               {0xf3553c65dacdadd2, "size:  a.out:  bad magic"},
+               {0x9d5e034087a676b9, "The major problem is with sendmail.  -Mark Horton"},
+               {0xa6db2d7f8da96417, "Give me a rock, paper and scissors and I will move the world.  CCFestoon"},
+               {0x325e00cd2fe819f9, "If the enemy is within range, then so are you."},
+               {0x88c6600ce58ae4c6, "It's well we cannot hear the screams/That we create in others' dreams."},
+               {0x28c4a3f3b769e078, "You remind me of a TV show, but that's all right: I watch it anyway."},
+               {0xa698a34c9d9f1dca, "C is as portable as Stonehedge!!"},
+               {0xf6c1e2a8c26c5cfc, "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"},
+               {0xd402559dfe9b70c, "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction.  Lewis-Randall Rule"},
+               {0xdb6efff26aa94946, "How can you write a big system without C++?  -Paul Glick"},
+       }
+
+       var tab = crc64.MakeTable(crc64.ISO)
+
+       for i := 0; i < len(crcGolden); i++ {
+               golden := crcGolden[i]
+               crc := NewCRC(tab, 0)
+               io.WriteString(crc, golden.in)
+               sum := crc.Sum64()
+
+               c.Assert(sum, Equals, golden.out)
+       }
+}
+
+// testCRC64Combine tests CRC64 on vector[0..pos] which should have CRC64 crc.
+// Also test CRC64Combine on vector[] split in two.
+func testCRC64Combine(c *C, str string, pos int, crc uint64) {
+       tabECMA := crc64.MakeTable(crc64.ECMA)
+
+       // Test CRC64
+       hash := crc64.New(tabECMA)
+       io.WriteString(hash, str)
+       crc1 := hash.Sum64()
+       c.Assert(crc1, Equals, crc)
+
+       // Test CRC64 combine
+       hash = crc64.New(tabECMA)
+       io.WriteString(hash, str[0:pos])
+       crc1 = hash.Sum64()
+
+       hash = crc64.New(tabECMA)
+       io.WriteString(hash, str[pos:len(str)])
+       crc2 := hash.Sum64()
+
+       crc1 = CRC64Combine(crc1, crc2, uint64(len(str)-pos))
+       c.Assert(crc1, Equals, crc)
+}
+
+// TestCRCCombine tests CRC64Combine
+func (s *OssCrcSuite) TestCRCCombine(c *C) {
+       str := "123456789"
+       testCRC64Combine(c, str, (len(str)+1)>>1, 0x995DC9BBDF1939FA)
+
+       str = "This is a test of the emergency broadcast system."
+       testCRC64Combine(c, str, (len(str)+1)>>1, 0x27DB187FC15BBC72)
+}
+
+// TestCRCRepeatedCombine tests CRC64Combine
+func (s *OssCrcSuite) TestCRCRepeatedCombine(c *C) {
+       tab := crc64.MakeTable(crc64.ECMA)
+       str := "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"
+
+       for i := 0; i <= len(str); i++ {
+               hash := crc64.New(tab)
+               io.WriteString(hash, string(str[0:i]))
+               prev := hash.Sum64()
+
+               hash = crc64.New(tab)
+               io.WriteString(hash, string(str[i:len(str)]))
+               post := hash.Sum64()
+
+               crc := CRC64Combine(prev, post, uint64(len(str)-i))
+               testLogger.Println("TestCRCRepeatedCombine:", prev, post, crc, i, len(str))
+               c.Assert(crc == 0x7AD25FAFA1710407, Equals, true)
+       }
+}
+
+// TestCRCRandomCombine tests CRC64Combine
+func (s *OssCrcSuite) TestCRCRandomCombine(c *C) {
+       tab := crc64.MakeTable(crc64.ECMA)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+
+       body, err := ioutil.ReadFile(fileName)
+       c.Assert(err, IsNil)
+
+       for i := 0; i < 10; i++ {
+               fileParts, err := SplitFileByPartNum(fileName, 1+rand.Intn(9999))
+               c.Assert(err, IsNil)
+
+               var crc uint64
+               for _, part := range fileParts {
+                       calc := NewCRC(tab, 0)
+                       calc.Write(body[part.Offset : part.Offset+part.Size])
+                       crc = CRC64Combine(crc, calc.Sum64(), (uint64)(part.Size))
+               }
+
+               testLogger.Println("TestCRCRandomCombine:", crc, i, fileParts)
+               c.Assert(crc == 0x2B612D24FFF64222, Equals, true)
+       }
+}
+
+// TestEnableCRCAndMD5 tests MD5 and CRC check
+func (s *OssCrcSuite) TestEnableCRCAndMD5(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFileName := "BingWallpaper-2015-11-07-2.jpg"
+       objectValue := "空山新雨后,天气晚来秋。明月松间照,清泉石上流。竹喧归浣女,莲动下渔舟。随意春芳歇,王孙自可留。"
+
+       client, err := New(endpoint, accessID, accessKey, EnableCRC(true), EnableMD5(true), MD5ThresholdCalcInMemory(200*1024))
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // PutObject
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // GetObject
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       _, err = ioutil.ReadAll(body)
+       c.Assert(err, IsNil)
+       body.Close()
+
+       // GetObjectWithCRC
+       getResult, err := bucket.DoGetObject(&GetObjectRequest{objectName}, nil)
+       c.Assert(err, IsNil)
+       str, err := readBody(getResult.Response.Body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+       c.Assert(getResult.ClientCRC.Sum64(), Equals, getResult.ServerCRC)
+
+       // PutObjectFromFile
+       err = bucket.PutObjectFromFile(objectName, fileName)
+       c.Assert(err, IsNil)
+
+       // GetObjectToFile
+       err = bucket.GetObjectToFile(objectName, newFileName)
+       c.Assert(err, IsNil)
+       eq, err := compareFiles(fileName, newFileName)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // DeleteObject
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // AppendObject
+       var nextPos int64
+       nextPos, err = bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos)
+       c.Assert(err, IsNil)
+       nextPos, err = bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos)
+       c.Assert(err, IsNil)
+
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       request := &AppendObjectRequest{
+               ObjectKey: objectName,
+               Reader:    strings.NewReader(objectValue),
+               Position:  0,
+       }
+       appendResult, err := bucket.DoAppendObject(request, []Option{InitCRC(0)})
+       c.Assert(err, IsNil)
+       request.Position = appendResult.NextPosition
+       appendResult, err = bucket.DoAppendObject(request, []Option{InitCRC(appendResult.CRC)})
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // MultipartUpload
+       chunks, err := SplitFileByPartSize(fileName, 100*1024)
+       imurUpload, err := bucket.InitiateMultipartUpload(objectName)
+       c.Assert(err, IsNil)
+       var partsUpload []UploadPart
+
+       for _, chunk := range chunks {
+               part, err := bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+               partsUpload = append(partsUpload, part)
+       }
+
+       _, err = bucket.CompleteMultipartUpload(imurUpload, partsUpload)
+       c.Assert(err, IsNil)
+
+       // Check MultipartUpload
+       err = bucket.GetObjectToFile(objectName, newFileName)
+       c.Assert(err, IsNil)
+       eq, err = compareFiles(fileName, newFileName)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // DeleteObjects
+       _, err = bucket.DeleteObjects([]string{objectName})
+       c.Assert(err, IsNil)
+}
+
+// TestDisableCRCAndMD5 disables MD5 and CRC
+func (s *OssCrcSuite) TestDisableCRCAndMD5(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFileName := "BingWallpaper-2015-11-07-3.jpg"
+       objectValue := "中岁颇好道,晚家南山陲。兴来每独往,胜事空自知。行到水穷处,坐看云起时。偶然值林叟,谈笑无还期。"
+
+       client, err := New(endpoint, accessID, accessKey, EnableCRC(false), EnableMD5(false))
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // PutObject
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // GetObject
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       _, err = ioutil.ReadAll(body)
+       c.Assert(err, IsNil)
+       body.Close()
+
+       // GetObjectWithCRC
+       getResult, err := bucket.DoGetObject(&GetObjectRequest{objectName}, nil)
+       c.Assert(err, IsNil)
+       str, err := readBody(getResult.Response.Body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, objectValue)
+
+       // PutObjectFromFile
+       err = bucket.PutObjectFromFile(objectName, fileName)
+       c.Assert(err, IsNil)
+
+       // GetObjectToFile
+       err = bucket.GetObjectToFile(objectName, newFileName)
+       c.Assert(err, IsNil)
+       eq, err := compareFiles(fileName, newFileName)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // DeleteObject
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // AppendObject
+       var nextPos int64
+       nextPos, err = bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos)
+       c.Assert(err, IsNil)
+       nextPos, err = bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos)
+       c.Assert(err, IsNil)
+
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       request := &AppendObjectRequest{
+               ObjectKey: objectName,
+               Reader:    strings.NewReader(objectValue),
+               Position:  0,
+       }
+       appendResult, err := bucket.DoAppendObject(request, []Option{InitCRC(0)})
+       c.Assert(err, IsNil)
+       request.Position = appendResult.NextPosition
+       appendResult, err = bucket.DoAppendObject(request, []Option{InitCRC(appendResult.CRC)})
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // MultipartUpload
+       chunks, err := SplitFileByPartSize(fileName, 100*1024)
+       imurUpload, err := bucket.InitiateMultipartUpload(objectName)
+       c.Assert(err, IsNil)
+       var partsUpload []UploadPart
+
+       for _, chunk := range chunks {
+               part, err := bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+               partsUpload = append(partsUpload, part)
+       }
+
+       _, err = bucket.CompleteMultipartUpload(imurUpload, partsUpload)
+       c.Assert(err, IsNil)
+
+       // Check MultipartUpload
+       err = bucket.GetObjectToFile(objectName, newFileName)
+       c.Assert(err, IsNil)
+       eq, err = compareFiles(fileName, newFileName)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // DeleteObjects
+       _, err = bucket.DeleteObjects([]string{objectName})
+       c.Assert(err, IsNil)
+}
+
+// TestSpecifyContentMD5 specifies MD5
+func (s *OssCrcSuite) TestSpecifyContentMD5(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       objectValue := "积雨空林烟火迟,蒸藜炊黍饷东菑。漠漠水田飞白鹭,阴阴夏木啭黄鹂。山中习静观朝槿,松下清斋折露葵。野老与人争席罢,海鸥何事更相疑。"
+
+       mh := md5.Sum([]byte(objectValue))
+       md5B64 := base64.StdEncoding.EncodeToString(mh[:])
+
+       // PutObject
+       err := s.bucket.PutObject(objectName, strings.NewReader(objectValue), ContentMD5(md5B64))
+       c.Assert(err, IsNil)
+
+       // PutObjectFromFile
+       file, err := os.Open(fileName)
+       md5 := md5.New()
+       io.Copy(md5, file)
+       mdHex := base64.StdEncoding.EncodeToString(md5.Sum(nil)[:])
+       err = s.bucket.PutObjectFromFile(objectName, fileName, ContentMD5(mdHex))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // AppendObject
+       var nextPos int64
+       nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos)
+       c.Assert(err, IsNil)
+       nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       request := &AppendObjectRequest{
+               ObjectKey: objectName,
+               Reader:    strings.NewReader(objectValue),
+               Position:  0,
+       }
+       appendResult, err := s.bucket.DoAppendObject(request, []Option{InitCRC(0)})
+       c.Assert(err, IsNil)
+       request.Position = appendResult.NextPosition
+       appendResult, err = s.bucket.DoAppendObject(request, []Option{InitCRC(appendResult.CRC)})
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // MultipartUpload
+       imurUpload, err := s.bucket.InitiateMultipartUpload(objectName)
+       c.Assert(err, IsNil)
+
+       var partsUpload []UploadPart
+       part, err := s.bucket.UploadPart(imurUpload, strings.NewReader(objectValue), (int64)(len([]byte(objectValue))), 1)
+       c.Assert(err, IsNil)
+       partsUpload = append(partsUpload, part)
+
+       _, err = s.bucket.CompleteMultipartUpload(imurUpload, partsUpload)
+       c.Assert(err, IsNil)
+
+       // DeleteObject
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestAppendObjectNegative
+func (s *OssCrcSuite) TestAppendObjectNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "空山不见人,但闻人语响。返影入深林,复照青苔上。"
+
+       nextPos, err := s.bucket.AppendObject(objectName, strings.NewReader(objectValue), 0, InitCRC(0))
+       c.Assert(err, IsNil)
+
+       nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos, InitCRC(0))
+       c.Assert(err, NotNil)
+       c.Assert(strings.HasPrefix(err.Error(), "oss: the crc"), Equals, true)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/aes_ctr.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/aes_ctr.go
new file mode 100644 (file)
index 0000000..ba7ab3f
--- /dev/null
@@ -0,0 +1,68 @@
+package osscrypto
+
+import (
+       "crypto/aes"
+       "crypto/cipher"
+       "io"
+)
+
+type aesCtr struct {
+       encrypter cipher.Stream
+       decrypter cipher.Stream
+}
+
+func newAesCtr(cd CipherData) (Cipher, error) {
+       block, err := aes.NewCipher(cd.Key)
+       if err != nil {
+               return nil, err
+       }
+
+       encrypter := cipher.NewCTR(block, cd.IV)
+       decrypter := cipher.NewCTR(block, cd.IV)
+       return &aesCtr{encrypter, decrypter}, nil
+}
+
+func (c *aesCtr) Encrypt(src io.Reader) io.Reader {
+       reader := &ctrEncryptReader{
+               encrypter: c.encrypter,
+               src:       src,
+       }
+       return reader
+}
+
+type ctrEncryptReader struct {
+       encrypter cipher.Stream
+       src       io.Reader
+}
+
+func (reader *ctrEncryptReader) Read(data []byte) (int, error) {
+       plainText := make([]byte, len(data), len(data))
+       n, err := reader.src.Read(plainText)
+       if n > 0 {
+               plainText = plainText[0:n]
+               reader.encrypter.XORKeyStream(data, plainText)
+       }
+       return n, err
+}
+
+func (c *aesCtr) Decrypt(src io.Reader) io.Reader {
+       return &ctrDecryptReader{
+               decrypter: c.decrypter,
+               src:       src,
+       }
+}
+
+type ctrDecryptReader struct {
+       decrypter cipher.Stream
+       src       io.Reader
+}
+
+func (reader *ctrDecryptReader) Read(data []byte) (int, error) {
+       cryptoText := make([]byte, len(data), len(data))
+       n, err := reader.src.Read(cryptoText)
+       if n > 0 {
+               cryptoText = cryptoText[0:n]
+               reader.decrypter.XORKeyStream(data, cryptoText)
+       }
+       return n, err
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/aes_ctr_cipher.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/aes_ctr_cipher.go
new file mode 100644 (file)
index 0000000..fa60bd0
--- /dev/null
@@ -0,0 +1,153 @@
+package osscrypto
+
+import (
+       "io"
+)
+
+const (
+       aesKeySize = 32
+       ivSize     = 16
+)
+
+// aesCtrCipherBuilder for building ContentCipher
+type aesCtrCipherBuilder struct {
+       MasterCipher MasterCipher
+}
+
+// aesCtrCipher will use aes ctr algorithm
+type aesCtrCipher struct {
+       CipherData CipherData
+       Cipher     Cipher
+}
+
+// CreateAesCtrCipher creates ContentCipherBuilder
+func CreateAesCtrCipher(cipher MasterCipher) ContentCipherBuilder {
+       return aesCtrCipherBuilder{MasterCipher: cipher}
+}
+
+// createCipherData create CipherData for encrypt object data
+func (builder aesCtrCipherBuilder) createCipherData() (CipherData, error) {
+       var cd CipherData
+       var err error
+       err = cd.RandomKeyIv(aesKeySize, ivSize)
+       if err != nil {
+               return cd, err
+       }
+
+       cd.WrapAlgorithm = builder.MasterCipher.GetWrapAlgorithm()
+       cd.CEKAlgorithm = AesCtrAlgorithm
+       cd.MatDesc = builder.MasterCipher.GetMatDesc()
+
+       // EncryptedKey
+       cd.EncryptedKey, err = builder.MasterCipher.Encrypt(cd.Key)
+       if err != nil {
+               return cd, err
+       }
+
+       // EncryptedIV
+       cd.EncryptedIV, err = builder.MasterCipher.Encrypt(cd.IV)
+       if err != nil {
+               return cd, err
+       }
+
+       return cd, nil
+}
+
+// contentCipherCD is used to create ContentCipher with CipherData
+func (builder aesCtrCipherBuilder) contentCipherCD(cd CipherData) (ContentCipher, error) {
+       cipher, err := newAesCtr(cd)
+       if err != nil {
+               return nil, err
+       }
+
+       return &aesCtrCipher{
+               CipherData: cd,
+               Cipher:     cipher,
+       }, nil
+}
+
+// ContentCipher is used to create ContentCipher interface
+func (builder aesCtrCipherBuilder) ContentCipher() (ContentCipher, error) {
+       cd, err := builder.createCipherData()
+       if err != nil {
+               return nil, err
+       }
+       return builder.contentCipherCD(cd)
+}
+
+// ContentCipherEnv is used to create a decrption ContentCipher from Envelope
+func (builder aesCtrCipherBuilder) ContentCipherEnv(envelope Envelope) (ContentCipher, error) {
+       var cd CipherData
+       cd.EncryptedKey = make([]byte, len(envelope.CipherKey))
+       copy(cd.EncryptedKey, []byte(envelope.CipherKey))
+
+       plainKey, err := builder.MasterCipher.Decrypt([]byte(envelope.CipherKey))
+       if err != nil {
+               return nil, err
+       }
+       cd.Key = make([]byte, len(plainKey))
+       copy(cd.Key, plainKey)
+
+       cd.EncryptedIV = make([]byte, len(envelope.IV))
+       copy(cd.EncryptedIV, []byte(envelope.IV))
+
+       plainIV, err := builder.MasterCipher.Decrypt([]byte(envelope.IV))
+       if err != nil {
+               return nil, err
+       }
+
+       cd.IV = make([]byte, len(plainIV))
+       copy(cd.IV, plainIV)
+
+       cd.MatDesc = envelope.MatDesc
+       cd.WrapAlgorithm = envelope.WrapAlg
+       cd.CEKAlgorithm = envelope.CEKAlg
+
+       return builder.contentCipherCD(cd)
+}
+
+// GetMatDesc is used to get MasterCipher's MatDesc
+func (builder aesCtrCipherBuilder) GetMatDesc() string {
+       return builder.MasterCipher.GetMatDesc()
+}
+
+// EncryptContents will generate a random key and iv and encrypt the data using ctr
+func (cc *aesCtrCipher) EncryptContent(src io.Reader) (io.ReadCloser, error) {
+       reader := cc.Cipher.Encrypt(src)
+       return &CryptoEncrypter{Body: src, Encrypter: reader}, nil
+}
+
+// DecryptContent is used to decrypt object using ctr
+func (cc *aesCtrCipher) DecryptContent(src io.Reader) (io.ReadCloser, error) {
+       reader := cc.Cipher.Decrypt(src)
+       return &CryptoDecrypter{Body: src, Decrypter: reader}, nil
+}
+
+// GetCipherData is used to get cipher data information
+func (cc *aesCtrCipher) GetCipherData() *CipherData {
+       return &(cc.CipherData)
+}
+
+// GetCipherData returns cipher data
+func (cc *aesCtrCipher) GetEncryptedLen(plainTextLen int64) int64 {
+       // AES CTR encryption mode does not change content length
+       return plainTextLen
+}
+
+// GetAlignLen is used to get align length
+func (cc *aesCtrCipher) GetAlignLen() int {
+       return len(cc.CipherData.IV)
+}
+
+// Clone is used to create a new aesCtrCipher from itself
+func (cc *aesCtrCipher) Clone(cd CipherData) (ContentCipher, error) {
+       cipher, err := newAesCtr(cd)
+       if err != nil {
+               return nil, err
+       }
+
+       return &aesCtrCipher{
+               CipherData: cd,
+               Cipher:     cipher,
+       }, nil
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/aes_ctr_cipher_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/aes_ctr_cipher_test.go
new file mode 100644 (file)
index 0000000..05f6a1e
--- /dev/null
@@ -0,0 +1,41 @@
+package osscrypto
+
+import (
+       . "gopkg.in/check.v1"
+)
+
+func (s *OssCryptoBucketSuite) TestContentEncryptCipherError(c *C) {
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       cc, err := contentProvider.ContentCipher()
+       c.Assert(err, IsNil)
+
+       var cipherData CipherData
+       cipherData.RandomKeyIv(31, 15)
+
+       _, err = cc.Clone(cipherData)
+       c.Assert(err, NotNil)
+}
+
+func (s *OssCryptoBucketSuite) TestCreateCipherDataError(c *C) {
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, "", "")
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+
+       v := contentProvider.(aesCtrCipherBuilder)
+       _, err := v.createCipherData()
+       c.Assert(err, NotNil)
+}
+
+func (s *OssCryptoBucketSuite) TestContentCipherCDError(c *C) {
+       var cd CipherData
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, "", "")
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+
+       v := contentProvider.(aesCtrCipherBuilder)
+       _, err := v.contentCipherCD(cd)
+       c.Assert(err, NotNil)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/cipher.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/cipher.go
new file mode 100644 (file)
index 0000000..7b512ea
--- /dev/null
@@ -0,0 +1,69 @@
+package osscrypto
+
+import (
+       "io"
+)
+
+// Cipher is interface for encryption or decryption of an object
+type Cipher interface {
+       Encrypter
+       Decrypter
+}
+
+// Encrypter is interface with only encrypt method
+type Encrypter interface {
+       Encrypt(io.Reader) io.Reader
+}
+
+// Decrypter is interface with only decrypt method
+type Decrypter interface {
+       Decrypt(io.Reader) io.Reader
+}
+
+// CryptoEncrypter provides close method for Encrypter
+type CryptoEncrypter struct {
+       Body      io.Reader
+       Encrypter io.Reader
+       isClosed  bool
+}
+
+// Close lets the CryptoEncrypter satisfy io.ReadCloser interface
+func (rc *CryptoEncrypter) Close() error {
+       rc.isClosed = true
+       if closer, ok := rc.Body.(io.ReadCloser); ok {
+               return closer.Close()
+       }
+       return nil
+}
+
+// Read lets the CryptoEncrypter satisfy io.ReadCloser interface
+func (rc *CryptoEncrypter) Read(b []byte) (int, error) {
+       if rc.isClosed {
+               return 0, io.EOF
+       }
+       return rc.Encrypter.Read(b)
+}
+
+// CryptoDecrypter provides close method for Decrypter
+type CryptoDecrypter struct {
+       Body      io.Reader
+       Decrypter io.Reader
+       isClosed  bool
+}
+
+// Close lets the CryptoDecrypter satisfy io.ReadCloser interface
+func (rc *CryptoDecrypter) Close() error {
+       rc.isClosed = true
+       if closer, ok := rc.Body.(io.ReadCloser); ok {
+               return closer.Close()
+       }
+       return nil
+}
+
+// Read lets the CryptoDecrypter satisfy io.ReadCloser interface
+func (rc *CryptoDecrypter) Read(b []byte) (int, error) {
+       if rc.isClosed {
+               return 0, io.EOF
+       }
+       return rc.Decrypter.Read(b)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/cipher_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/cipher_test.go
new file mode 100644 (file)
index 0000000..4cb013d
--- /dev/null
@@ -0,0 +1,32 @@
+package osscrypto
+
+import (
+       "io"
+       "strings"
+
+       . "gopkg.in/check.v1"
+)
+
+func (s *OssCryptoBucketSuite) TestAesCtr(c *C) {
+       var cipherData CipherData
+       cipherData.RandomKeyIv(32, 16)
+       cipher, _ := newAesCtr(cipherData)
+
+       byteReader := strings.NewReader(RandLowStr(100))
+       enReader := cipher.Encrypt(byteReader)
+       encrypter := &CryptoEncrypter{Body: byteReader, Encrypter: enReader}
+       encrypter.Close()
+       buff := make([]byte, 10)
+       n, err := encrypter.Read(buff)
+       c.Assert(n, Equals, 0)
+       c.Assert(err, Equals, io.EOF)
+
+       deReader := cipher.Encrypt(byteReader)
+       Decrypter := &CryptoDecrypter{Body: byteReader, Decrypter: deReader}
+       Decrypter.Close()
+       buff = make([]byte, 10)
+       n, err = Decrypter.Read(buff)
+       c.Assert(n, Equals, 0)
+       c.Assert(err, Equals, io.EOF)
+
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_bucket.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_bucket.go
new file mode 100644 (file)
index 0000000..1efeeeb
--- /dev/null
@@ -0,0 +1,539 @@
+package osscrypto
+
+import (
+       "encoding/base64"
+       "encoding/json"
+       "fmt"
+       "hash"
+       "hash/crc64"
+       "io"
+       "net/http"
+       "os"
+       "strconv"
+
+       kms "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// MasterCipherManager is interface for getting master key with MatDesc(material desc)
+// If you may use different master keys for encrypting and decrypting objects,each master
+// key must have a unique, non-emtpy, unalterable MatDesc(json string format) and you must provide this interface
+// If you always use the same master key for encrypting and decrypting objects, MatDesc
+// can be empty and you don't need to provide this interface
+//
+// matDesc map[string]string:is converted by matDesc json string
+// return: []string  the secret key information,such as {"rsa-public-key","rsa-private-key"} or {"non-rsa-key"}
+type MasterCipherManager interface {
+       GetMasterKey(matDesc map[string]string) ([]string, error)
+}
+
+// ExtraCipherBuilder is interface for creating a decrypt ContentCipher with Envelope
+// If the objects you need to decrypt are neither encrypted with ContentCipherBuilder
+// you provided, nor encrypted with rsa and ali kms master keys, you must provide this interface
+//
+// ContentCipher  the interface used to decrypt objects
+type ExtraCipherBuilder interface {
+       GetDecryptCipher(envelope Envelope, cm MasterCipherManager) (ContentCipher, error)
+}
+
+// CryptoBucketOption CryptoBucket option such as SetAliKmsClient, SetMasterCipherManager, SetDecryptCipherManager.
+type CryptoBucketOption func(*CryptoBucket)
+
+// SetAliKmsClient set field AliKmsClient of CryptoBucket
+// If the objects you need to decrypt are encrypted with ali kms master key,but not with ContentCipherBuilder
+// you provided, you must provide this interface
+func SetAliKmsClient(client *kms.Client) CryptoBucketOption {
+       return func(bucket *CryptoBucket) {
+               bucket.AliKmsClient = client
+       }
+}
+
+// SetMasterCipherManager set field MasterCipherManager of CryptoBucket
+func SetMasterCipherManager(manager MasterCipherManager) CryptoBucketOption {
+       return func(bucket *CryptoBucket) {
+               bucket.MasterCipherManager = manager
+       }
+}
+
+// SetExtraCipherBuilder set field ExtraCipherBuilder of CryptoBucket
+func SetExtraCipherBuilder(extraBuilder ExtraCipherBuilder) CryptoBucketOption {
+       return func(bucket *CryptoBucket) {
+               bucket.ExtraCipherBuilder = extraBuilder
+       }
+}
+
+// DefaultExtraCipherBuilder is Default implementation of the ExtraCipherBuilder for rsa and kms master keys
+type DefaultExtraCipherBuilder struct {
+       AliKmsClient *kms.Client
+}
+
+// GetDecryptCipher is used to get ContentCipher for decrypt object
+func (decb *DefaultExtraCipherBuilder) GetDecryptCipher(envelope Envelope, cm MasterCipherManager) (ContentCipher, error) {
+       if cm == nil {
+               return nil, fmt.Errorf("DefaultExtraCipherBuilder GetDecryptCipher error,MasterCipherManager is nil")
+       }
+
+       if envelope.CEKAlg != AesCtrAlgorithm {
+               return nil, fmt.Errorf("DefaultExtraCipherBuilder GetDecryptCipher error,not supported content algorithm %s", envelope.CEKAlg)
+       }
+
+       if envelope.WrapAlg != RsaCryptoWrap && envelope.WrapAlg != KmsAliCryptoWrap {
+               return nil, fmt.Errorf("DefaultExtraCipherBuilder GetDecryptCipher error,not supported envelope wrap algorithm %s", envelope.WrapAlg)
+       }
+
+       matDesc := make(map[string]string)
+       if envelope.MatDesc != "" {
+               err := json.Unmarshal([]byte(envelope.MatDesc), &matDesc)
+               if err != nil {
+                       return nil, err
+               }
+       }
+
+       masterKeys, err := cm.GetMasterKey(matDesc)
+       if err != nil {
+               return nil, err
+       }
+
+       var contentCipher ContentCipher
+       if envelope.WrapAlg == RsaCryptoWrap {
+               // for rsa master key
+               if len(masterKeys) != 2 {
+                       return nil, fmt.Errorf("rsa keys count must be 2,now is %d", len(masterKeys))
+               }
+               rsaCipher, err := CreateMasterRsa(matDesc, masterKeys[0], masterKeys[1])
+               if err != nil {
+                       return nil, err
+               }
+               aesCtrBuilder := CreateAesCtrCipher(rsaCipher)
+               contentCipher, err = aesCtrBuilder.ContentCipherEnv(envelope)
+
+       } else if envelope.WrapAlg == KmsAliCryptoWrap {
+               // for kms master key
+               if len(masterKeys) != 1 {
+                       return nil, fmt.Errorf("non-rsa keys count must be 1,now is %d", len(masterKeys))
+               }
+
+               if decb.AliKmsClient == nil {
+                       return nil, fmt.Errorf("aliyun kms client is nil")
+               }
+
+               kmsCipher, err := CreateMasterAliKms(matDesc, masterKeys[0], decb.AliKmsClient)
+               if err != nil {
+                       return nil, err
+               }
+               aesCtrBuilder := CreateAesCtrCipher(kmsCipher)
+               contentCipher, err = aesCtrBuilder.ContentCipherEnv(envelope)
+       } else {
+               // to do
+               // for master keys which are neither rsa nor kms
+       }
+
+       return contentCipher, err
+}
+
+// CryptoBucket implements the operations for encrypting and decrypting objects
+// ContentCipherBuilder is used to encrypt and decrypt objects by default
+// when the object's MatDesc which you want to decrypt is emtpy or same to the
+// master key's MatDesc you provided in ContentCipherBuilder, sdk try to
+// use ContentCipherBuilder to decrypt
+type CryptoBucket struct {
+       oss.Bucket
+       ContentCipherBuilder ContentCipherBuilder
+       ExtraCipherBuilder   ExtraCipherBuilder
+       MasterCipherManager  MasterCipherManager
+       AliKmsClient         *kms.Client
+}
+
+// GetCryptoBucket create a client encyrption bucket
+func GetCryptoBucket(client *oss.Client, bucketName string, builder ContentCipherBuilder,
+       options ...CryptoBucketOption) (*CryptoBucket, error) {
+       var cryptoBucket CryptoBucket
+       cryptoBucket.Client = *client
+       cryptoBucket.BucketName = bucketName
+       cryptoBucket.ContentCipherBuilder = builder
+
+       for _, option := range options {
+               option(&cryptoBucket)
+       }
+
+       if cryptoBucket.ExtraCipherBuilder == nil {
+               cryptoBucket.ExtraCipherBuilder = &DefaultExtraCipherBuilder{AliKmsClient: cryptoBucket.AliKmsClient}
+       }
+
+       return &cryptoBucket, nil
+}
+
+// PutObject creates a new object and encyrpt it on client side when uploading to oss
+func (bucket CryptoBucket) PutObject(objectKey string, reader io.Reader, options ...oss.Option) error {
+       options = bucket.AddEncryptionUaSuffix(options)
+       cc, err := bucket.ContentCipherBuilder.ContentCipher()
+       if err != nil {
+               return err
+       }
+
+       cryptoReader, err := cc.EncryptContent(reader)
+       if err != nil {
+               return err
+       }
+
+       var request *oss.PutObjectRequest
+       srcLen, err := oss.GetReaderLen(reader)
+       if err != nil {
+               request = &oss.PutObjectRequest{
+                       ObjectKey: objectKey,
+                       Reader:    cryptoReader,
+               }
+       } else {
+               encryptedLen := cc.GetEncryptedLen(srcLen)
+               request = &oss.PutObjectRequest{
+                       ObjectKey: objectKey,
+                       Reader:    oss.LimitReadCloser(cryptoReader, encryptedLen),
+               }
+       }
+
+       opts := addCryptoHeaders(options, cc.GetCipherData())
+       resp, err := bucket.DoPutObject(request, opts)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return err
+}
+
+// GetObject downloads the object from oss
+// If the object is encrypted, sdk decrypt it automaticly
+func (bucket CryptoBucket) GetObject(objectKey string, options ...oss.Option) (io.ReadCloser, error) {
+       options = bucket.AddEncryptionUaSuffix(options)
+       result, err := bucket.DoGetObject(&oss.GetObjectRequest{ObjectKey: objectKey}, options)
+       if err != nil {
+               return nil, err
+       }
+       return result.Response, nil
+}
+
+// GetObjectToFile downloads the object from oss to local file
+// If the object is encrypted, sdk decrypt it automaticly
+func (bucket CryptoBucket) GetObjectToFile(objectKey, filePath string, options ...oss.Option) error {
+       options = bucket.AddEncryptionUaSuffix(options)
+       tempFilePath := filePath + oss.TempFileSuffix
+
+       // Calls the API to actually download the object. Returns the result instance.
+       result, err := bucket.DoGetObject(&oss.GetObjectRequest{ObjectKey: objectKey}, options)
+       if err != nil {
+               return err
+       }
+       defer result.Response.Close()
+
+       // If the local file does not exist, create a new one. If it exists, overwrite it.
+       fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, oss.FilePermMode)
+       if err != nil {
+               return err
+       }
+
+       // Copy the data to the local file path.
+       _, err = io.Copy(fd, result.Response.Body)
+       fd.Close()
+       if err != nil {
+               return err
+       }
+
+       // Compares the CRC value
+       hasRange, _, _ := oss.IsOptionSet(options, oss.HTTPHeaderRange)
+       encodeOpt, _ := oss.FindOption(options, oss.HTTPHeaderAcceptEncoding, nil)
+       acceptEncoding := ""
+       if encodeOpt != nil {
+               acceptEncoding = encodeOpt.(string)
+       }
+       if bucket.GetConfig().IsEnableCRC && !hasRange && acceptEncoding != "gzip" {
+               result.Response.ClientCRC = result.ClientCRC.Sum64()
+               err = oss.CheckCRC(result.Response, "GetObjectToFile")
+               if err != nil {
+                       os.Remove(tempFilePath)
+                       return err
+               }
+       }
+
+       return os.Rename(tempFilePath, filePath)
+}
+
+// DoGetObject is the actual API that gets the encrypted or not encrypted object.
+// It's the internal function called by other public APIs.
+func (bucket CryptoBucket) DoGetObject(request *oss.GetObjectRequest, options []oss.Option) (*oss.GetObjectResult, error) {
+       options = bucket.AddEncryptionUaSuffix(options)
+
+       // first,we must head object
+       metaInfo, err := bucket.GetObjectDetailedMeta(request.ObjectKey)
+       if err != nil {
+               return nil, err
+       }
+
+       isEncryptedObj := isEncryptedObject(metaInfo)
+       if !isEncryptedObj {
+               return bucket.Bucket.DoGetObject(request, options)
+       }
+
+       envelope, err := getEnvelopeFromHeader(metaInfo)
+       if err != nil {
+               return nil, err
+       }
+
+       if !isValidContentAlg(envelope.CEKAlg) {
+               return nil, fmt.Errorf("not supported content algorithm %s,object:%s", envelope.CEKAlg, request.ObjectKey)
+       }
+
+       if !envelope.IsValid() {
+               return nil, fmt.Errorf("getEnvelopeFromHeader error,object:%s", request.ObjectKey)
+       }
+
+       // use ContentCipherBuilder to decrpt object by default
+       encryptMatDesc := bucket.ContentCipherBuilder.GetMatDesc()
+       var cc ContentCipher
+       err = nil
+       if envelope.MatDesc == encryptMatDesc {
+               cc, err = bucket.ContentCipherBuilder.ContentCipherEnv(envelope)
+       } else {
+               cc, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
+       }
+
+       if err != nil {
+               return nil, fmt.Errorf("%s,object:%s", err.Error(), request.ObjectKey)
+       }
+
+       discardFrontAlignLen := int64(0)
+       uRange, err := oss.GetRangeConfig(options)
+       if err != nil {
+               return nil, err
+       }
+
+       if uRange != nil && uRange.HasStart {
+               // process range to align key size
+               adjustStart := adjustRangeStart(uRange.Start, cc)
+               discardFrontAlignLen = uRange.Start - adjustStart
+               if discardFrontAlignLen > 0 {
+                       uRange.Start = adjustStart
+                       options = oss.DeleteOption(options, oss.HTTPHeaderRange)
+                       options = append(options, oss.NormalizedRange(oss.GetRangeString(*uRange)))
+               }
+
+               // seek iv
+               cipherData := cc.GetCipherData().Clone()
+               cipherData.SeekIV(uint64(adjustStart))
+               cc, _ = cc.Clone(cipherData)
+       }
+
+       params, _ := oss.GetRawParams(options)
+       resp, err := bucket.Do("GET", request.ObjectKey, params, options, nil, nil)
+       if err != nil {
+               return nil, err
+       }
+
+       result := &oss.GetObjectResult{
+               Response: resp,
+       }
+
+       // CRC
+       var crcCalc hash.Hash64
+       hasRange, _, _ := oss.IsOptionSet(options, oss.HTTPHeaderRange)
+       if bucket.GetConfig().IsEnableCRC && !hasRange {
+               crcCalc = crc64.New(oss.CrcTable())
+               result.ServerCRC = resp.ServerCRC
+               result.ClientCRC = crcCalc
+       }
+
+       // Progress
+       listener := oss.GetProgressListener(options)
+       contentLen, _ := strconv.ParseInt(resp.Headers.Get(oss.HTTPHeaderContentLength), 10, 64)
+       resp.Body = oss.TeeReader(resp.Body, crcCalc, contentLen, listener, nil)
+       resp.Body, err = cc.DecryptContent(resp.Body)
+       if err == nil && discardFrontAlignLen > 0 {
+               resp.Body = &oss.DiscardReadCloser{
+                       RC:      resp.Body,
+                       Discard: int(discardFrontAlignLen)}
+       }
+       return result, err
+}
+
+// PutObjectFromFile creates a new object from the local file
+// the object will be encrypted automaticly on client side when uploaded to oss
+func (bucket CryptoBucket) PutObjectFromFile(objectKey, filePath string, options ...oss.Option) error {
+       options = bucket.AddEncryptionUaSuffix(options)
+       fd, err := os.Open(filePath)
+       if err != nil {
+               return err
+       }
+       defer fd.Close()
+
+       opts := oss.AddContentType(options, filePath, objectKey)
+       cc, err := bucket.ContentCipherBuilder.ContentCipher()
+       if err != nil {
+               return err
+       }
+
+       cryptoReader, err := cc.EncryptContent(fd)
+       if err != nil {
+               return err
+       }
+
+       var request *oss.PutObjectRequest
+       srcLen, err := oss.GetReaderLen(fd)
+       if err != nil {
+               request = &oss.PutObjectRequest{
+                       ObjectKey: objectKey,
+                       Reader:    cryptoReader,
+               }
+       } else {
+               encryptedLen := cc.GetEncryptedLen(srcLen)
+               request = &oss.PutObjectRequest{
+                       ObjectKey: objectKey,
+                       Reader:    oss.LimitReadCloser(cryptoReader, encryptedLen),
+               }
+       }
+
+       opts = addCryptoHeaders(opts, cc.GetCipherData())
+       resp, err := bucket.DoPutObject(request, opts)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return nil
+}
+
+// AppendObject please refer to Bucket.AppendObject
+func (bucket CryptoBucket) AppendObject(objectKey string, reader io.Reader, appendPosition int64, options ...oss.Option) (int64, error) {
+       return 0, fmt.Errorf("CryptoBucket doesn't support AppendObject")
+}
+
+// DoAppendObject please refer to Bucket.DoAppendObject
+func (bucket CryptoBucket) DoAppendObject(request *oss.AppendObjectRequest, options []oss.Option) (*oss.AppendObjectResult, error) {
+       return nil, fmt.Errorf("CryptoBucket doesn't support DoAppendObject")
+}
+
+// PutObjectWithURL please refer to Bucket.PutObjectWithURL
+func (bucket CryptoBucket) PutObjectWithURL(signedURL string, reader io.Reader, options ...oss.Option) error {
+       return fmt.Errorf("CryptoBucket doesn't support PutObjectWithURL")
+}
+
+// PutObjectFromFileWithURL please refer to Bucket.PutObjectFromFileWithURL
+func (bucket CryptoBucket) PutObjectFromFileWithURL(signedURL, filePath string, options ...oss.Option) error {
+       return fmt.Errorf("CryptoBucket doesn't support PutObjectFromFileWithURL")
+}
+
+// DoPutObjectWithURL please refer to Bucket.DoPutObjectWithURL
+func (bucket CryptoBucket) DoPutObjectWithURL(signedURL string, reader io.Reader, options []oss.Option) (*oss.Response, error) {
+       return nil, fmt.Errorf("CryptoBucket doesn't support DoPutObjectWithURL")
+}
+
+// GetObjectWithURL please refer to Bucket.GetObjectWithURL
+func (bucket CryptoBucket) GetObjectWithURL(signedURL string, options ...oss.Option) (io.ReadCloser, error) {
+       return nil, fmt.Errorf("CryptoBucket doesn't support GetObjectWithURL")
+}
+
+// GetObjectToFileWithURL please refer to Bucket.GetObjectToFileWithURL
+func (bucket CryptoBucket) GetObjectToFileWithURL(signedURL, filePath string, options ...oss.Option) error {
+       return fmt.Errorf("CryptoBucket doesn't support GetObjectToFileWithURL")
+}
+
+// DoGetObjectWithURL please refer to Bucket.DoGetObjectWithURL
+func (bucket CryptoBucket) DoGetObjectWithURL(signedURL string, options []oss.Option) (*oss.GetObjectResult, error) {
+       return nil, fmt.Errorf("CryptoBucket doesn't support DoGetObjectWithURL")
+}
+
+// ProcessObject please refer to Bucket.ProcessObject
+func (bucket CryptoBucket) ProcessObject(objectKey string, process string, options ...oss.Option) (oss.ProcessObjectResult, error) {
+       var out oss.ProcessObjectResult
+       return out, fmt.Errorf("CryptoBucket doesn't support ProcessObject")
+}
+
+func (bucket CryptoBucket) AddEncryptionUaSuffix(options []oss.Option) []oss.Option {
+       var outOption []oss.Option
+       bSet, _, _ := oss.IsOptionSet(options, oss.HTTPHeaderUserAgent)
+       if bSet || bucket.Client.Config.UserSetUa {
+               outOption = options
+               return outOption
+       }
+       outOption = append(options, oss.UserAgentHeader(bucket.Client.Config.UserAgent+"/"+EncryptionUaSuffix))
+       return outOption
+}
+
+// isEncryptedObject judge the object is encrypted or not
+func isEncryptedObject(headers http.Header) bool {
+       encrptedKey := headers.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionKey)
+       return len(encrptedKey) > 0
+}
+
+// addCryptoHeaders save Envelope information in oss meta
+func addCryptoHeaders(options []oss.Option, cd *CipherData) []oss.Option {
+       opts := []oss.Option{}
+
+       // convert content-md5
+       md5Option, _ := oss.FindOption(options, oss.HTTPHeaderContentMD5, nil)
+       if md5Option != nil {
+               opts = append(opts, oss.Meta(OssClientSideEncryptionUnencryptedContentMD5, md5Option.(string)))
+               options = oss.DeleteOption(options, oss.HTTPHeaderContentMD5)
+       }
+
+       // convert content-length
+       lenOption, _ := oss.FindOption(options, oss.HTTPHeaderContentLength, nil)
+       if lenOption != nil {
+               opts = append(opts, oss.Meta(OssClientSideEncryptionUnencryptedContentLength, lenOption.(string)))
+               options = oss.DeleteOption(options, oss.HTTPHeaderContentLength)
+       }
+
+       opts = append(opts, options...)
+
+       // matDesc
+       if cd.MatDesc != "" {
+               opts = append(opts, oss.Meta(OssClientSideEncryptionMatDesc, cd.MatDesc))
+       }
+
+       // encrypted key
+       strEncryptedKey := base64.StdEncoding.EncodeToString(cd.EncryptedKey)
+       opts = append(opts, oss.Meta(OssClientSideEncryptionKey, strEncryptedKey))
+
+       // encrypted iv
+       strEncryptedIV := base64.StdEncoding.EncodeToString(cd.EncryptedIV)
+       opts = append(opts, oss.Meta(OssClientSideEncryptionStart, strEncryptedIV))
+
+       // wrap alg
+       opts = append(opts, oss.Meta(OssClientSideEncryptionWrapAlg, cd.WrapAlgorithm))
+
+       // cek alg
+       opts = append(opts, oss.Meta(OssClientSideEncryptionCekAlg, cd.CEKAlgorithm))
+
+       return opts
+}
+
+func getEnvelopeFromHeader(header http.Header) (Envelope, error) {
+       var envelope Envelope
+       envelope.IV = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionStart)
+       decodedIV, err := base64.StdEncoding.DecodeString(envelope.IV)
+       if err != nil {
+               return envelope, err
+       }
+       envelope.IV = string(decodedIV)
+
+       envelope.CipherKey = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionKey)
+       decodedKey, err := base64.StdEncoding.DecodeString(envelope.CipherKey)
+       if err != nil {
+               return envelope, err
+       }
+       envelope.CipherKey = string(decodedKey)
+
+       envelope.MatDesc = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionMatDesc)
+       envelope.WrapAlg = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionWrapAlg)
+       envelope.CEKAlg = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionCekAlg)
+       envelope.UnencryptedMD5 = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionUnencryptedContentMD5)
+       envelope.UnencryptedContentLen = header.Get(oss.HTTPHeaderOssMetaPrefix + OssClientSideEncryptionUnencryptedContentLength)
+       return envelope, err
+}
+
+func isValidContentAlg(algName string) bool {
+       // now content encyrption only support aec/ctr algorithm
+       return algName == AesCtrAlgorithm
+}
+
+func adjustRangeStart(start int64, cc ContentCipher) int64 {
+       alignLen := int64(cc.GetAlignLen())
+       return (start / alignLen) * alignLen
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_bucket_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_bucket_test.go
new file mode 100644 (file)
index 0000000..a2c4d11
--- /dev/null
@@ -0,0 +1,1226 @@
+package osscrypto
+
+import (
+       "crypto/md5"
+       "encoding/hex"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "log"
+       "math/rand"
+       math_rand "math/rand"
+       "net/http"
+       "os"
+       "strings"
+       "testing"
+       "time"
+
+       kms "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+       . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) {
+       TestingT(t)
+}
+
+type OssCryptoBucketSuite struct {
+}
+
+var _ = Suite(&OssCryptoBucketSuite{})
+
+var (
+       matDesc = make(map[string]string)
+
+       rsaPublicKey string = `-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCokfiAVXXf5ImFzKDw+XO/UByW
+6mse2QsIgz3ZwBtMNu59fR5zttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC
+5MFO1PByrE/MNd5AAfSVba93I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR
+1EKib1Id8hpooY5xaQIDAQAB
+-----END PUBLIC KEY-----`
+
+       rsaPrivateKey string = `-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKiR+IBVdd/kiYXM
+oPD5c79QHJbqax7ZCwiDPdnAG0w27n19HnO21LH7x8Hu9HgI3dtPO2s/0DpuOg3Q
+UWeGVDe80kLkwU7U8HKsT8w13kAB9JVtr3cjqzHw1KTkzNQIDg0nMBSpg4RYa0YF
+yibqQQXoyZHUQqJvUh3yGmihjnFpAgMBAAECgYA49RmCQ14QyKevDfVTdvYlLmx6
+kbqgMbYIqk+7w611kxoCTMR9VMmJWgmk/Zic9mIAOEVbd7RkCdqT0E+xKzJJFpI2
+ZHjrlwb21uqlcUqH1Gn+wI+jgmrafrnKih0kGucavr/GFi81rXixDrGON9KBE0FJ
+cPVdc0XiQAvCBnIIAQJBANXu3htPH0VsSznfqcDE+w8zpoAJdo6S/p30tcjsDQnx
+l/jYV4FXpErSrtAbmI013VYkdJcghNSLNUXppfk2e8UCQQDJt5c07BS9i2SDEXiz
+byzqCfXVzkdnDj9ry9mba1dcr9B9NCslVelXDGZKvQUBqNYCVxg398aRfWlYDTjU
+IoVVAkAbTyjPN6R4SkC4HJMg5oReBmvkwFCAFsemBk0GXwuzD0IlJAjXnAZ+/rIO
+ItewfwXIL1Mqz53lO/gK+q6TR585AkB304KUIoWzjyF3JqLP3IQOxzns92u9EV6l
+V2P+CkbMPXiZV6sls6I4XppJXX2i3bu7iidN3/dqJ9izQK94fMU9AkBZvgsIPCot
+y1/POIbv9LtnviDKrmpkXgVQSU4BmTPvXwTJm8APC7P/horSh3SVf1zgmnsyjm9D
+hO92gGc+4ajL
+-----END PRIVATE KEY-----`
+
+       rsaPublicKeyPks1 string = `-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBAKiR+IBVdd/kiYXMoPD5c79QHJbqax7ZCwiDPdnAG0w27n19HnO21LH7
+x8Hu9HgI3dtPO2s/0DpuOg3QUWeGVDe80kLkwU7U8HKsT8w13kAB9JVtr3cjqzHw
+1KTkzNQIDg0nMBSpg4RYa0YFyibqQQXoyZHUQqJvUh3yGmihjnFpAgMBAAE=
+-----END RSA PUBLIC KEY-----`
+
+       rsaPrivateKeyPks1 string = `-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQCokfiAVXXf5ImFzKDw+XO/UByW6mse2QsIgz3ZwBtMNu59fR5z
+ttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC5MFO1PByrE/MNd5AAfSVba93
+I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR1EKib1Id8hpooY5xaQIDAQAB
+AoGAOPUZgkNeEMinrw31U3b2JS5sepG6oDG2CKpPu8OtdZMaAkzEfVTJiVoJpP2Y
+nPZiADhFW3e0ZAnak9BPsSsySRaSNmR465cG9tbqpXFKh9Rp/sCPo4Jq2n65yood
+JBrnGr6/xhYvNa14sQ6xjjfSgRNBSXD1XXNF4kALwgZyCAECQQDV7t4bTx9FbEs5
+36nAxPsPM6aACXaOkv6d9LXI7A0J8Zf42FeBV6RK0q7QG5iNNd1WJHSXIITUizVF
+6aX5NnvFAkEAybeXNOwUvYtkgxF4s28s6gn11c5HZw4/a8vZm2tXXK/QfTQrJVXp
+VwxmSr0FAajWAlcYN/fGkX1pWA041CKFVQJAG08ozzekeEpAuByTIOaEXgZr5MBQ
+gBbHpgZNBl8Lsw9CJSQI15wGfv6yDiLXsH8FyC9TKs+d5Tv4Cvquk0efOQJAd9OC
+lCKFs48hdyaiz9yEDsc57PdrvRFepVdj/gpGzD14mVerJbOiOF6aSV19ot27u4on
+Td/3aifYs0CveHzFPQJAWb4LCDwqLctfzziG7/S7Z74gyq5qZF4FUElOAZkz718E
+yZvADwuz/4aK0od0lX9c4Jp7Mo5vQ4TvdoBnPuGoyw==
+-----END RSA PRIVATE KEY-----`
+)
+
+var (
+       // Endpoint/ID/Key
+       endpoint         = os.Getenv("OSS_TEST_ENDPOINT")
+       accessID         = os.Getenv("OSS_TEST_ACCESS_KEY_ID")
+       accessKey        = os.Getenv("OSS_TEST_ACCESS_KEY_SECRET")
+       kmsID            = os.Getenv("OSS_TEST_KMS_ID")
+       kmsRegion        = os.Getenv("OSS_TEST_KMS_REGION")
+       kmsAccessID      = accessID
+       kmsAccessKey     = accessKey
+       bucketNamePrefix = "go-sdk-test-bucket-"
+       objectNamePrefix = "go-sdk-test-object-"
+)
+
+var (
+       logPath            = "go_sdk_test_" + time.Now().Format("20060102_150405") + ".log"
+       testLogFile, _     = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE, 0664)
+       testLogger         = log.New(testLogFile, "", log.Ldate|log.Ltime|log.Lshortfile)
+       letters            = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+       timeoutInOperation = 3 * time.Second
+)
+
+func RandStr(n int) string {
+       b := make([]rune, n)
+       randMarker := rand.New(rand.NewSource(time.Now().UnixNano()))
+       for i := range b {
+               b[i] = letters[randMarker.Intn(len(letters))]
+       }
+       return string(b)
+}
+
+func RandLowStr(n int) string {
+       return strings.ToLower(RandStr(n))
+}
+
+func GetFileMD5(filePath string) (string, error) {
+       fd, err := os.Open(filePath)
+       if err != nil {
+               return "", err
+       }
+       defer fd.Close()
+
+       md5 := md5.New()
+       _, err = io.Copy(md5, fd)
+       if err != nil {
+               return "", fmt.Errorf("buff copy error")
+       }
+       md5Str := hex.EncodeToString(md5.Sum(nil))
+       return md5Str, nil
+}
+
+func GetStringMd5(s string) string {
+       md5 := md5.New()
+       md5.Write([]byte(s))
+       md5Str := hex.EncodeToString(md5.Sum(nil))
+       return md5Str
+}
+
+func ForceDeleteBucket(client *oss.Client, bucketName string, c *C) {
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // Delete Object
+       marker := oss.Marker("")
+       for {
+               lor, err := bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = oss.Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete Object Versions and DeleteMarks
+       keyMarker := oss.KeyMarker("")
+       versionIdMarker := oss.VersionIdMarker("")
+       options := []oss.Option{keyMarker, versionIdMarker}
+       for {
+               lor, err := bucket.ListObjectVersions(options...)
+               if err != nil {
+                       break
+               }
+
+               for _, object := range lor.ObjectDeleteMarkers {
+                       err = bucket.DeleteObject(object.Key, oss.VersionId(object.VersionId))
+                       c.Assert(err, IsNil)
+               }
+
+               for _, object := range lor.ObjectVersions {
+                       err = bucket.DeleteObject(object.Key, oss.VersionId(object.VersionId))
+                       c.Assert(err, IsNil)
+               }
+
+               keyMarker = oss.KeyMarker(lor.NextKeyMarker)
+               versionIdMarker := oss.VersionIdMarker(lor.NextVersionIdMarker)
+               options = []oss.Option{keyMarker, versionIdMarker}
+
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete Part
+       keyMarker = oss.KeyMarker("")
+       uploadIDMarker := oss.UploadIDMarker("")
+       for {
+               lmur, err := bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+               c.Assert(err, IsNil)
+               for _, upload := range lmur.Uploads {
+                       var imur = oss.InitiateMultipartUploadResult{Bucket: bucketName,
+                               Key: upload.Key, UploadID: upload.UploadID}
+                       err = bucket.AbortMultipartUpload(imur)
+                       c.Assert(err, IsNil)
+               }
+               keyMarker = oss.KeyMarker(lmur.NextKeyMarker)
+               uploadIDMarker = oss.UploadIDMarker(lmur.NextUploadIDMarker)
+               if !lmur.IsTruncated {
+                       break
+               }
+       }
+
+       // delete live channel
+       strMarker := ""
+       for {
+               result, err := bucket.ListLiveChannel(oss.Marker(strMarker))
+               c.Assert(err, IsNil)
+
+               for _, channel := range result.LiveChannel {
+                       err := bucket.DeleteLiveChannel(channel.Name)
+                       c.Assert(err, IsNil)
+               }
+
+               if result.IsTruncated {
+                       strMarker = result.NextMarker
+               } else {
+                       break
+               }
+       }
+
+       // Delete Bucket
+       err = client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+}
+
+func ReadBody(body io.ReadCloser) (string, error) {
+       data, err := ioutil.ReadAll(body)
+       body.Close()
+       if err != nil {
+               return "", err
+       }
+       return string(data), nil
+}
+
+// SetUpSuite runs once when the suite starts running
+func (s *OssCryptoBucketSuite) SetUpSuite(c *C) {
+}
+
+// TearDownSuite runs before each test or benchmark starts running
+func (s *OssCryptoBucketSuite) TearDownSuite(c *C) {
+}
+
+// SetUpTest runs after each test or benchmark runs
+func (s *OssCryptoBucketSuite) SetUpTest(c *C) {
+}
+
+// TearDownTest runs once after all tests or benchmarks have finished running
+func (s *OssCryptoBucketSuite) TearDownTest(c *C) {
+
+}
+
+func (s *OssCryptoBucketSuite) TestPutObjectNormalPks8(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       testMatDesc := make(map[string]string)
+       testMatDesc["desc"] = "test rsa key"
+       masterRsaCipher, _ := CreateMasterRsa(testMatDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(1023)
+
+       // Put string
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), oss.GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err := ReadBody(body)
+       c.Assert(text, Equals, objectValue)
+
+       // non-crypto bucket download
+       normalBucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       body, err = normalBucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       encryptText, err := ReadBody(body)
+       c.Assert(encryptText != objectValue, Equals, true)
+
+       // acl
+       acl, err := bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, "default")
+
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // put with meta
+       options := []oss.Option{
+               oss.ObjectACL(oss.ACLPublicRead),
+               oss.Meta("myprop", "mypropval"),
+       }
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err = ReadBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(text, Equals, objectValue)
+
+       acl, err = bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(oss.ACLPublicRead))
+
+       meta, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestPutObjectNormalPks1(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKeyPks1, rsaPrivateKeyPks1)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(1023)
+
+       // Put string
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), oss.GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err := ReadBody(body)
+       c.Assert(text, Equals, objectValue)
+
+       // non-crypto bucket download
+       normalBucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       body, err = normalBucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       encryptText, err := ReadBody(body)
+       c.Assert(encryptText != objectValue, Equals, true)
+
+       // acl
+       acl, err := bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, "default")
+
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // put with meta
+       options := []oss.Option{
+               oss.ObjectACL(oss.ACLPublicRead),
+               oss.Meta("myprop", "mypropval"),
+       }
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err = ReadBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(text, Equals, objectValue)
+
+       acl, err = bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(oss.ACLPublicRead))
+
+       meta, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestPutObjectEmptyPks1(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKeyPks1, rsaPrivateKeyPks1)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := ""
+
+       // Put empty string
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), oss.GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err := ReadBody(body)
+       c.Assert(text, Equals, objectValue)
+
+       // non-crypto bucket download
+       normalBucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       body, err = normalBucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       encryptText, err := ReadBody(body)
+       c.Assert(encryptText == objectValue, Equals, true)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestPutObjectSmallSizePks1(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKeyPks1, rsaPrivateKeyPks1)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := "123"
+
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), oss.GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err := ReadBody(body)
+       c.Assert(text, Equals, objectValue)
+
+       // non-crypto bucket download
+       normalBucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       body, err = normalBucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       encryptText, err := ReadBody(body)
+       c.Assert(encryptText != objectValue, Equals, true)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestPutObjectEmptyFilePks1(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKeyPks1, rsaPrivateKeyPks1)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       fileName := "oss-go-sdk-test-file-" + RandStr(5)
+       fo, err := os.Create(fileName)
+       c.Assert(err, IsNil)
+       _, err = fo.Write([]byte(""))
+       c.Assert(err, IsNil)
+       fo.Close()
+
+       objectName := objectNamePrefix + RandStr(8)
+
+       // file not exist
+       err = bucket.PutObjectFromFile(objectName, "/root1/abc.txt")
+       c.Assert(err, NotNil)
+
+       err = bucket.PutObjectFromFile(objectName, fileName)
+       c.Assert(err, IsNil)
+
+       downFileName := fileName + "-down"
+
+       // Check
+       err = bucket.GetObjectToFile(objectName, downFileName)
+       c.Assert(err, IsNil)
+
+       b1, err := ioutil.ReadFile(fileName)
+       b2, err := ioutil.ReadFile(downFileName)
+       c.Assert(len(b1), Equals, 0)
+       c.Assert(string(b1), Equals, string(b2))
+
+       os.Remove(downFileName)
+       os.Remove(fileName)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestKmsPutObjectNormal(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       kmsClient, err := kms.NewClientWithAccessKey(kmsRegion, kmsAccessID, kmsAccessKey)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterKmsCipher, _ := CreateMasterAliKms(matDesc, kmsID, kmsClient)
+       contentProvider := CreateAesCtrCipher(masterKmsCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(1023)
+
+       // Put string
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), oss.GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err := ReadBody(body)
+       c.Assert(text, Equals, objectValue)
+
+       // non-crypto bucket download
+       normalBucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       body, err = normalBucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       encryptText, err := ReadBody(body)
+       c.Assert(encryptText != objectValue, Equals, true)
+
+       // acl
+       acl, err := bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, "default")
+
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // put with meta
+       options := []oss.Option{
+               oss.ObjectACL(oss.ACLPublicRead),
+               oss.Meta("myprop", "mypropval"),
+       }
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err = bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err = ReadBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(text, Equals, objectValue)
+
+       acl, err = bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(acl.ACL, Equals, string(oss.ACLPublicRead))
+
+       meta, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       // put object error,bucket not exist
+       bucket.BucketName = bucket.BucketName + "-not-exist"
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), options...)
+       c.Assert(err, NotNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+type MockKmsManager struct {
+}
+
+func (mg *MockKmsManager) GetMasterKey(matDesc map[string]string) ([]string, error) {
+       if len(matDesc) == 0 {
+               return nil, fmt.Errorf("not found")
+       }
+
+       keyList := []string{kmsID}
+       return keyList, nil
+}
+
+func (s *OssCryptoBucketSuite) TestRsaBucketDecrptObjectWithKmsSuccess(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       kmsClient, err := kms.NewClientWithAccessKey(kmsRegion, kmsAccessID, kmsAccessKey)
+       c.Assert(err, IsNil)
+
+       // crypto bucket with kms
+       testMatDesc := make(map[string]string)
+       testMatDesc["desc"] = "test kms wrap"
+       masterKmsCipher, _ := CreateMasterAliKms(testMatDesc, kmsID, kmsClient)
+       contentProvider := CreateAesCtrCipher(masterKmsCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(1023)
+
+       // Put string
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), oss.GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       // crypto bucket with rsa
+       var masterManager MockKmsManager
+       var options []CryptoBucketOption
+       options = append(options, SetAliKmsClient(kmsClient))
+       options = append(options, SetMasterCipherManager(&masterManager))
+
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       rsaProvider := CreateAesCtrCipher(masterRsaCipher)
+       rsaBucket, err := GetCryptoBucket(client, bucketName, rsaProvider, options...)
+
+       // Check
+       body, err := rsaBucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err := ReadBody(body)
+       c.Assert(text, Equals, objectValue)
+
+       // non-crypto bucket download
+       normalBucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       body, err = normalBucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       encryptText, err := ReadBody(body)
+       c.Assert(encryptText != objectValue, Equals, true)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestRsaBucketDecrptObjectWithKmsError(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       kmsClient, err := kms.NewClientWithAccessKey(kmsRegion, kmsAccessID, kmsAccessKey)
+       c.Assert(err, IsNil)
+
+       // crypto bucket with kms
+       testMatDesc := make(map[string]string)
+       testMatDesc["desc"] = "test kms wrap"
+       masterKmsCipher, _ := CreateMasterAliKms(testMatDesc, kmsID, kmsClient)
+       contentProvider := CreateAesCtrCipher(masterKmsCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(1023)
+
+       // Put string
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), oss.GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       // crypto bucket with rsa
+       var masterManager MockKmsManager
+       var options []CryptoBucketOption
+
+       // kms client is nil
+       //options = append(options, SetAliKmsClient(kmsClient))
+
+       options = append(options, SetMasterCipherManager(&masterManager))
+
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       rsaProvider := CreateAesCtrCipher(masterRsaCipher)
+       rsaBucket, err := GetCryptoBucket(client, bucketName, rsaProvider, options...)
+
+       // Check
+       _, err = rsaBucket.GetObject(objectName)
+       c.Assert(err, NotNil)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestRangeGetObject(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       contentLen := 1024 * 1024
+       content := RandStr(contentLen)
+       err = bucket.PutObject(objectName, strings.NewReader(content))
+       c.Assert(err, IsNil)
+
+       // range get
+       for i := 0; i < 20; i++ {
+               math_rand.Seed(time.Now().UnixNano())
+               rangeStart := rand.Intn(contentLen)
+               rangeEnd := rangeStart + rand.Intn(contentLen-rangeStart)
+               if rangeEnd == rangeStart || rangeStart >= contentLen-1 {
+                       continue
+               }
+
+               body, err := bucket.GetObject(objectName, oss.Range(int64(rangeStart), int64(rangeEnd)))
+               c.Assert(err, IsNil)
+               downText, err := ReadBody(body)
+               c.Assert(len(downText) > 0, Equals, true)
+               downMd5 := GetStringMd5(downText)
+
+               srcText := content[rangeStart : rangeEnd+1]
+               srcMd5 := GetStringMd5(srcText)
+
+               c.Assert(len(downText), Equals, len(srcText))
+               c.Assert(downMd5, Equals, srcMd5)
+       }
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestGetNormalObject(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       // normal bucket
+       normalBucket, _ := client.Bucket(bucketName)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(1023)
+
+       // Put string
+       err = normalBucket.PutObject(objectName, strings.NewReader(objectValue))
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err := ReadBody(body)
+       c.Assert(text, Equals, objectValue)
+
+       // delete object
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // get object again
+       body, err = bucket.GetObject(objectName)
+       c.Assert(err, NotNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestGetCryptoBucketNotSupport(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(1023)
+
+       // AppendObject
+       _, err = bucket.AppendObject(objectName, strings.NewReader(objectValue), 0)
+       c.Assert(err, NotNil)
+
+       // DoAppendObject
+       var request oss.AppendObjectRequest
+       var options []oss.Option
+       _, err = bucket.DoAppendObject(&request, options)
+       c.Assert(err, NotNil)
+
+       // PutObjectWithURL
+       err = bucket.PutObjectWithURL("oss://bucket/object", strings.NewReader(objectValue))
+       c.Assert(err, NotNil)
+
+       // PutObjectFromFileWithURL
+       err = bucket.PutObjectFromFileWithURL("oss://bucket/object", "file.txt")
+       c.Assert(err, NotNil)
+
+       // DoPutObjectWithURL
+       _, err = bucket.DoPutObjectWithURL("oss://bucket/object", strings.NewReader(objectValue), options)
+       c.Assert(err, NotNil)
+
+       // GetObjectWithURL
+       _, err = bucket.GetObjectWithURL("oss://bucket/object")
+       c.Assert(err, NotNil)
+
+       // GetObjectToFileWithURL
+       err = bucket.GetObjectToFileWithURL("oss://bucket/object", "file.txt")
+       c.Assert(err, NotNil)
+
+       // DoGetObjectWithURL
+       _, err = bucket.DoGetObjectWithURL("oss://bucket/object", options)
+       c.Assert(err, NotNil)
+
+       // ProcessObject
+       _, err = bucket.ProcessObject("oss://bucket/object", "")
+       c.Assert(err, NotNil)
+
+       // DownloadFile
+       err = bucket.DownloadFile(objectName, "file.txt", 1024)
+       c.Assert(err, NotNil)
+
+       // CopyFile
+       err = bucket.CopyFile("src-bucket", "src-object", "dest-object", 1024)
+       c.Assert(err, NotNil)
+
+       // UploadFile
+       err = bucket.UploadFile(objectName, "file.txt", 1024)
+       c.Assert(err, NotNil)
+}
+
+type MockRsaManager struct {
+}
+
+func (mg *MockRsaManager) GetMasterKey(matDesc map[string]string) ([]string, error) {
+       if len(matDesc) == 0 {
+               return nil, fmt.Errorf("not found")
+       }
+
+       keyList := []string{rsaPublicKey, rsaPrivateKey}
+       return keyList, nil
+}
+
+func (s *OssCryptoBucketSuite) TestGetMasterKey(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       testMatDesc := make(map[string]string)
+       testMatDesc["desc"] = "test rsa key"
+       masterRsaCipher, _ := CreateMasterRsa(testMatDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+
+       fileName := "../../sample/BingWallpaper-2015-11-07.jpg"
+       srcMD5, err := GetFileMD5(fileName)
+       c.Assert(err, IsNil)
+
+       err = bucket.PutObjectFromFile(objectName, fileName)
+       c.Assert(err, IsNil)
+
+       // other crypto bucket
+       var rsaManager MockRsaManager
+       masterRsaCipherOther, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProviderOther := CreateAesCtrCipher(masterRsaCipherOther)
+       bucketOther, err := GetCryptoBucket(client, bucketName, contentProviderOther, SetMasterCipherManager(&rsaManager))
+
+       //  download
+       downfileName := "test-go-sdk-file-" + RandLowStr(5) + ".jpg"
+       err = bucketOther.GetObjectToFile(objectName, downfileName)
+       c.Assert(err, IsNil)
+       downFileMD5, err := GetFileMD5(downfileName)
+       c.Assert(err, IsNil)
+       c.Assert(downFileMD5, Equals, srcMD5)
+
+       // GetObjectToFile error
+       err = bucketOther.GetObjectToFile(objectName, "/root1/"+downfileName)
+       c.Assert(err, NotNil)
+
+       os.Remove(downfileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+type MockReader struct {
+       Reader io.Reader
+}
+
+func (r *MockReader) Read(b []byte) (int, error) {
+       return r.Reader.Read(b)
+}
+
+func (s *OssCryptoBucketSuite) TestPutObjectUnkownReaderLen(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(1023)
+
+       srcMD5 := GetStringMd5(objectValue)
+       options := []oss.Option{oss.ContentMD5(srcMD5), oss.ContentLength(1023)}
+
+       // Put string
+       mockReader := &MockReader{strings.NewReader(objectValue)}
+       err = bucket.PutObject(objectName, mockReader, options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       body, err := bucket.GetObject(objectName)
+       c.Assert(err, IsNil)
+       text, err := ReadBody(body)
+       c.Assert(text, Equals, objectValue)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestGetDecryptCipher(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       var rsaManager MockRsaManager
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider, SetMasterCipherManager(&rsaManager))
+
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(1023)
+
+       // Put string
+       var respHeader http.Header
+       err = bucket.PutObject(objectName, strings.NewReader(objectValue), oss.GetResponseHeader(&respHeader))
+       c.Assert(err, IsNil)
+
+       // first,we must head object
+       metaInfo, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+
+       envelope, _ := getEnvelopeFromHeader(metaInfo)
+
+       // test for getEnvelopeFromHeader
+       metaInfo.Set(oss.HTTPHeaderOssMetaPrefix+OssClientSideEncryptionKey, string([]byte{200, 200, 200}))
+       _, err = getEnvelopeFromHeader(metaInfo)
+       c.Assert(err, NotNil)
+       metaInfo.Set(oss.HTTPHeaderOssMetaPrefix+OssClientSideEncryptionKey, envelope.CipherKey)
+
+       metaInfo.Set(oss.HTTPHeaderOssMetaPrefix+OssClientSideEncryptionStart, string([]byte{200, 200, 200}))
+       _, err = getEnvelopeFromHeader(metaInfo)
+       c.Assert(err, NotNil)
+       metaInfo.Set(oss.HTTPHeaderOssMetaPrefix+OssClientSideEncryptionKey, envelope.IV)
+
+       // test for getDecryptCipher
+       CEKAlg := envelope.CEKAlg
+       envelope.CEKAlg = ""
+       _, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
+       c.Assert(err, NotNil)
+       envelope.CEKAlg = CEKAlg
+
+       // matDesc is emtpy
+       bucket.MasterCipherManager = &MockRsaManager{}
+       _, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
+       c.Assert(err, NotNil)
+
+       // MasterCipherManager is nil
+       bucket.MasterCipherManager = nil
+       _, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
+       c.Assert(err, NotNil)
+
+       WrapAlg := envelope.WrapAlg
+       envelope.WrapAlg = "test"
+       _, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
+       c.Assert(err, NotNil)
+       envelope.WrapAlg = WrapAlg
+
+       envelope.WrapAlg = KmsAliCryptoWrap
+       _, err = bucket.ExtraCipherBuilder.GetDecryptCipher(envelope, bucket.MasterCipherManager)
+       c.Assert(err, NotNil)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestGetObjectEncryptedByCppRsa(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // put object encrypted by cpp
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+       srcJpgFile := "../../sample/test-client-encryption-src.jpg"
+       fileEncryptedByCpp := "../../sample/test-client-encryption-crypto-cpp-rsa.jpg"
+
+       opts := []oss.Option{}
+       opts = append(opts, oss.Meta(OssClientSideEncryptionKey, "nyXOp7delQ/MQLjKQMhHLaT0w7u2yQoDLkSnK8MFg/MwYdh4na4/LS8LLbLcM18m8I/ObWUHU775I50sJCpdv+f4e0jLeVRRiDFWe+uo7Puc9j4xHj8YB3QlcIOFQiTxHIB6q+C+RA6lGwqqYVa+n3aV5uWhygyv1MWmESurppg="))
+       opts = append(opts, oss.Meta(OssClientSideEncryptionStart, "De/S3T8wFjx7QPxAAFl7h7TeI2EsZlfCwox4WhLGng5DK2vNXxULmulMUUpYkdc9umqmDilgSy5Z3Foafw+v4JJThfw68T/9G2gxZLrQTbAlvFPFfPM9Ehk6cY4+8WpY32uN8w5vrHyoSZGr343NxCUGIp6fQ9sSuOLMoJg7hNw="))
+       opts = append(opts, oss.Meta(OssClientSideEncryptionWrapAlg, "RSA/NONE/PKCS1Padding"))
+       opts = append(opts, oss.Meta(OssClientSideEncryptionCekAlg, "AES/CTR/NoPadding"))
+       err = bucket.PutObjectFromFile(objectName, fileEncryptedByCpp, opts...)
+       c.Assert(err, IsNil)
+
+       // download with crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       cryptoBucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       downFileName := "oss-go-sdk-test-file-" + RandStr(5)
+       err = cryptoBucket.GetObjectToFile(objectName, downFileName)
+       c.Assert(err, IsNil)
+
+       downMd5, _ := GetFileMD5(downFileName)
+       srcJpgMd5, _ := GetFileMD5(srcJpgFile)
+       c.Assert(downMd5, Equals, srcJpgMd5)
+       os.Remove(downFileName)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestGetObjectEncryptedByPythonRsa(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // put object encrypted by python
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+       srcJpgFile := "../../sample/test-client-encryption-src.jpg"
+       fileEncryptedByCpp := "../../sample/test-client-encryption-crypto-python-rsa.jpg"
+
+       opts := []oss.Option{}
+       opts = append(opts, oss.Meta(OssClientSideEncryptionKey, "ZNQM4g+JykUfOBMkfL8kbvChD3R23UH53sRyTg42h9H2ph8ZJJlo2tSP5Oi3nR5gJAwA/OTrruNq02M2Zt4N7zVWdbFArKbY/CkHpihVYOqsSU4Z8RmrNBm4QfC5om2WElRHNt8hlqhnvzhdorGDB5OoMQ8KvQqXDC53aM5OY64="))
+       opts = append(opts, oss.Meta(OssClientSideEncryptionStart, "mZ6kts6kaMm++0akhQQZl+tj8gPWznZ+giHciCQTIzriwBzZZO4d85YZeBStuUPshdnO3QHK63/NH9QFL6pwpLiXI9UZxkGygkp82oB4jaF4HKoQ4ujd670pXLxpljBLnp0sCxiCIaf5Fzp4jgNCurXycY10/5DN7yPPtdw7dkk="))
+       opts = append(opts, oss.Meta(OssClientSideEncryptionWrapAlg, "RSA/NONE/PKCS1Padding"))
+       opts = append(opts, oss.Meta(OssClientSideEncryptionCekAlg, "AES/CTR/NoPadding"))
+       err = bucket.PutObjectFromFile(objectName, fileEncryptedByCpp, opts...)
+       c.Assert(err, IsNil)
+
+       // download with crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       cryptoBucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       downFileName := "oss-go-sdk-test-file-" + RandStr(5)
+       err = cryptoBucket.GetObjectToFile(objectName, downFileName)
+       c.Assert(err, IsNil)
+
+       downMd5, _ := GetFileMD5(downFileName)
+       srcJpgMd5, _ := GetFileMD5(srcJpgFile)
+       c.Assert(downMd5, Equals, srcJpgMd5)
+       os.Remove(downFileName)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestRepeatedPutObjectFromFile(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+       srcJpgFile := "../../sample/test-client-encryption-src.jpg"
+
+       // put object from file
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       cryptoBucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       err = cryptoBucket.PutObjectFromFile(objectName, srcJpgFile)
+       c.Assert(err, IsNil)
+
+       downFileName := "oss-go-sdk-test-file-" + RandStr(5)
+       err = cryptoBucket.GetObjectToFile(objectName, downFileName)
+       c.Assert(err, IsNil)
+
+       srcJpgMd5, _ := GetFileMD5(srcJpgFile)
+       downMd5, _ := GetFileMD5(downFileName)
+       c.Assert(len(srcJpgMd5) > 0, Equals, true)
+       c.Assert(len(downMd5) > 0, Equals, true)
+       c.Assert(downMd5, Equals, srcJpgMd5)
+       os.Remove(downFileName)
+
+       err = cryptoBucket.PutObjectFromFile(objectName+"-other", srcJpgFile)
+       c.Assert(err, IsNil)
+       err = cryptoBucket.GetObjectToFile(objectName, downFileName)
+       c.Assert(err, IsNil)
+       downMd5, _ = GetFileMD5(downFileName)
+       c.Assert(downMd5, Equals, srcJpgMd5)
+
+       os.Remove(downFileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestPutObjectEncryptionUserAgent(c *C) {
+       logName := "." + string(os.PathSeparator) + "test-go-sdk.log" + RandStr(5)
+       f, err := os.OpenFile(logName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
+       c.Assert(err, IsNil)
+
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       client.Config.LogLevel = oss.Debug
+       client.Config.Logger = log.New(f, "", log.LstdFlags)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+       srcJpgFile := "../../sample/test-client-encryption-src.jpg"
+
+       // put object from file
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       cryptoBucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       err = cryptoBucket.PutObjectFromFile(objectName, srcJpgFile)
+       c.Assert(err, IsNil)
+
+       // read log file,get http info
+       contents, err := ioutil.ReadFile(logName)
+       c.Assert(err, IsNil)
+
+       httpContent := string(contents)
+       c.Assert(strings.Contains(httpContent, EncryptionUaSuffix), Equals, true)
+
+       f.Close()
+       os.Remove(logName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestPutObjectNormalUserAgent(c *C) {
+       logName := "." + string(os.PathSeparator) + "test-go-sdk.log" + RandStr(5)
+       f, err := os.OpenFile(logName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
+       c.Assert(err, IsNil)
+
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       client.Config.LogLevel = oss.Debug
+       client.Config.Logger = log.New(f, "", log.LstdFlags)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+       srcJpgFile := "../../sample/test-client-encryption-src.jpg"
+
+       bucket, err := client.Bucket(bucketName)
+
+       err = bucket.PutObjectFromFile(objectName, srcJpgFile)
+       c.Assert(err, IsNil)
+
+       // read log file,get http info
+       contents, err := ioutil.ReadFile(logName)
+       c.Assert(err, IsNil)
+
+       httpContent := string(contents)
+       c.Assert(strings.Contains(httpContent, EncryptionUaSuffix), Equals, false)
+
+       f.Close()
+       os.Remove(logName)
+       ForceDeleteBucket(client, bucketName, c)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_const.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_const.go
new file mode 100644 (file)
index 0000000..73f4622
--- /dev/null
@@ -0,0 +1,26 @@
+package osscrypto
+
+// for client sider encryption oss meta
+const (
+       OssClientSideEncryptionKey                      string = "client-side-encryption-key"
+       OssClientSideEncryptionStart                           = "client-side-encryption-start"
+       OssClientSideEncryptionCekAlg                          = "client-side-encryption-cek-alg"
+       OssClientSideEncryptionWrapAlg                         = "client-side-encryption-wrap-alg"
+       OssClientSideEncryptionMatDesc                         = "client-side-encryption-matdesc"
+       OssClientSideEncryptionUnencryptedContentLength        = "client-side-encryption-unencrypted-content-length"
+       OssClientSideEncryptionUnencryptedContentMD5           = "client-side-encryption-unencrypted-content-md5"
+       OssClientSideEncryptionDataSize                        = "client-side-encryption-data-size"
+       OssClientSideEncryptionPartSize                        = "client-side-encryption-part-size"
+)
+
+// encryption Algorithm
+const (
+       RsaCryptoWrap    string = "RSA/NONE/PKCS1Padding"
+       KmsAliCryptoWrap string = "KMS/ALICLOUD"
+       AesCtrAlgorithm  string = "AES/CTR/NoPadding"
+)
+
+// user agent tag for client encryption
+const (
+       EncryptionUaSuffix string = "OssEncryptionClient"
+)
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_download.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_download.go
new file mode 100644 (file)
index 0000000..2c924e0
--- /dev/null
@@ -0,0 +1,12 @@
+package osscrypto
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// DownloadFile with multi part mode, temporarily not supported
+func (bucket CryptoBucket) DownloadFile(objectKey, filePath string, partSize int64, options ...oss.Option) error {
+       return fmt.Errorf("CryptoBucket doesn't support DownloadFile")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_multicopy.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_multicopy.go
new file mode 100644 (file)
index 0000000..331466c
--- /dev/null
@@ -0,0 +1,12 @@
+package osscrypto
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// CopyFile with multi part mode, temporarily not supported
+func (bucket CryptoBucket) CopyFile(srcBucketName, srcObjectKey, destObjectKey string, partSize int64, options ...oss.Option) error {
+       return fmt.Errorf("CryptoBucket doesn't support CopyFile")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_multipart.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_multipart.go
new file mode 100644 (file)
index 0000000..afa0783
--- /dev/null
@@ -0,0 +1,174 @@
+package osscrypto
+
+import (
+       "fmt"
+       "io"
+       "os"
+       "strconv"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// PartCryptoContext save encryption or decryption information
+type PartCryptoContext struct {
+       ContentCipher ContentCipher
+       DataSize      int64
+       PartSize      int64
+}
+
+// Valid judge PartCryptoContext is valid or not
+func (pcc PartCryptoContext) Valid() bool {
+       if pcc.ContentCipher == nil || pcc.DataSize == 0 || pcc.PartSize == 0 {
+               return false
+       }
+       return true
+}
+
+// InitiateMultipartUpload initializes multipart upload for client encryption
+// cryptoContext.PartSize and cryptoContext.DataSize are input parameter
+// cryptoContext.PartSize must aligned to the secret iv length
+// cryptoContext.ContentCipher is output parameter
+// cryptoContext will be used in next API
+func (bucket CryptoBucket) InitiateMultipartUpload(objectKey string, cryptoContext *PartCryptoContext, options ...oss.Option) (oss.InitiateMultipartUploadResult, error) {
+       options = bucket.AddEncryptionUaSuffix(options)
+       var imur oss.InitiateMultipartUploadResult
+       if cryptoContext == nil {
+               return imur, fmt.Errorf("error,cryptoContext is nil")
+       }
+
+       if cryptoContext.PartSize <= 0 {
+               return imur, fmt.Errorf("invalid PartCryptoContext's PartSize %d", cryptoContext.PartSize)
+       }
+
+       cc, err := bucket.ContentCipherBuilder.ContentCipher()
+       if err != nil {
+               return imur, err
+       }
+
+       if cryptoContext.PartSize%int64(cc.GetAlignLen()) != 0 {
+               return imur, fmt.Errorf("PartCryptoContext's PartSize must be aligned to %d", cc.GetAlignLen())
+       }
+
+       opts := addCryptoHeaders(options, cc.GetCipherData())
+       if cryptoContext.DataSize > 0 {
+               opts = append(opts, oss.Meta(OssClientSideEncryptionDataSize, strconv.FormatInt(cryptoContext.DataSize, 10)))
+       }
+       opts = append(opts, oss.Meta(OssClientSideEncryptionPartSize, strconv.FormatInt(cryptoContext.PartSize, 10)))
+
+       imur, err = bucket.Bucket.InitiateMultipartUpload(objectKey, opts...)
+       if err == nil {
+               cryptoContext.ContentCipher = cc
+       }
+       return imur, err
+}
+
+// UploadPart uploads parts to oss, the part data are encrypted automaticly on client side
+// cryptoContext is the input parameter
+func (bucket CryptoBucket) UploadPart(imur oss.InitiateMultipartUploadResult, reader io.Reader,
+       partSize int64, partNumber int, cryptoContext PartCryptoContext, options ...oss.Option) (oss.UploadPart, error) {
+       options = bucket.AddEncryptionUaSuffix(options)
+       var uploadPart oss.UploadPart
+       if cryptoContext.ContentCipher == nil {
+               return uploadPart, fmt.Errorf("error,cryptoContext is nil or cryptoContext.ContentCipher is nil")
+       }
+
+       if partNumber < 1 {
+               return uploadPart, fmt.Errorf("partNumber:%d is smaller than 1", partNumber)
+       }
+
+       if cryptoContext.PartSize%int64(cryptoContext.ContentCipher.GetAlignLen()) != 0 {
+               return uploadPart, fmt.Errorf("PartCryptoContext's PartSize must be aligned to %d", cryptoContext.ContentCipher.GetAlignLen())
+       }
+
+       cipherData := cryptoContext.ContentCipher.GetCipherData().Clone()
+       // caclulate iv based on part number
+       if partNumber > 1 {
+               cipherData.SeekIV(uint64(partNumber-1) * uint64(cryptoContext.PartSize))
+       }
+
+       // for parallel upload part
+       partCC, _ := cryptoContext.ContentCipher.Clone(cipherData)
+
+       cryptoReader, err := partCC.EncryptContent(reader)
+       if err != nil {
+               return uploadPart, err
+       }
+
+       request := &oss.UploadPartRequest{
+               InitResult: &imur,
+               Reader:     cryptoReader,
+               PartSize:   partCC.GetEncryptedLen(partSize),
+               PartNumber: partNumber,
+       }
+
+       opts := addCryptoHeaders(options, partCC.GetCipherData())
+       if cryptoContext.DataSize > 0 {
+               opts = append(opts, oss.Meta(OssClientSideEncryptionDataSize, strconv.FormatInt(cryptoContext.DataSize, 10)))
+       }
+       opts = append(opts, oss.Meta(OssClientSideEncryptionPartSize, strconv.FormatInt(cryptoContext.PartSize, 10)))
+
+       result, err := bucket.Bucket.DoUploadPart(request, opts)
+       return result.Part, err
+}
+
+// UploadPartFromFile uploads part from the file, the part data are encrypted automaticly on client side
+// cryptoContext is the input parameter
+func (bucket CryptoBucket) UploadPartFromFile(imur oss.InitiateMultipartUploadResult, filePath string,
+       startPosition, partSize int64, partNumber int, cryptoContext PartCryptoContext, options ...oss.Option) (oss.UploadPart, error) {
+       options = bucket.AddEncryptionUaSuffix(options)
+       var uploadPart = oss.UploadPart{}
+       if cryptoContext.ContentCipher == nil {
+               return uploadPart, fmt.Errorf("error,cryptoContext is nil or cryptoContext.ContentCipher is nil")
+       }
+
+       if cryptoContext.PartSize%int64(cryptoContext.ContentCipher.GetAlignLen()) != 0 {
+               return uploadPart, fmt.Errorf("PartCryptoContext's PartSize must be aligned to %d", cryptoContext.ContentCipher.GetAlignLen())
+       }
+
+       fd, err := os.Open(filePath)
+       if err != nil {
+               return uploadPart, err
+       }
+       defer fd.Close()
+       fd.Seek(startPosition, os.SEEK_SET)
+
+       if partNumber < 1 {
+               return uploadPart, fmt.Errorf("partNumber:%d is smaller than 1", partNumber)
+       }
+
+       cipherData := cryptoContext.ContentCipher.GetCipherData().Clone()
+       // calculate iv based on part number
+       if partNumber > 1 {
+               cipherData.SeekIV(uint64(partNumber-1) * uint64(cryptoContext.PartSize))
+       }
+
+       // for parallel upload part
+       partCC, _ := cryptoContext.ContentCipher.Clone(cipherData)
+       cryptoReader, err := partCC.EncryptContent(fd)
+       if err != nil {
+               return uploadPart, err
+       }
+
+       encryptedLen := partCC.GetEncryptedLen(partSize)
+       opts := addCryptoHeaders(options, partCC.GetCipherData())
+       if cryptoContext.DataSize > 0 {
+               opts = append(opts, oss.Meta(OssClientSideEncryptionDataSize, strconv.FormatInt(cryptoContext.DataSize, 10)))
+       }
+       opts = append(opts, oss.Meta(OssClientSideEncryptionPartSize, strconv.FormatInt(cryptoContext.PartSize, 10)))
+
+       request := &oss.UploadPartRequest{
+               InitResult: &imur,
+               Reader:     cryptoReader,
+               PartSize:   encryptedLen,
+               PartNumber: partNumber,
+       }
+       result, err := bucket.Bucket.DoUploadPart(request, opts)
+       return result.Part, err
+}
+
+// UploadPartCopy uploads part copy
+func (bucket CryptoBucket) UploadPartCopy(imur oss.InitiateMultipartUploadResult, srcBucketName, srcObjectKey string,
+       startPosition, partSize int64, partNumber int, cryptoContext PartCryptoContext, options ...oss.Option) (oss.UploadPart, error) {
+       var uploadPart = oss.UploadPart{}
+       return uploadPart, fmt.Errorf("CryptoBucket doesn't support UploadPartCopy")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_multipart_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_multipart_test.go
new file mode 100644 (file)
index 0000000..1283c79
--- /dev/null
@@ -0,0 +1,758 @@
+// multipart test
+
+package osscrypto
+
+import (
+       "io/ioutil"
+       "os"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+       . "gopkg.in/check.v1"
+)
+
+func (s *OssCryptoBucketSuite) TestMultipartUpload(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../../sample/BingWallpaper-2015-11-07.jpg"
+
+       srcMD5, err := GetFileMD5(fileName)
+       c.Assert(err, IsNil)
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       options := []oss.Option{oss.Meta("my", "myprop")}
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = (dataSize / 16 / 3) * 16
+       imur, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext, options...)
+       c.Assert(err, IsNil)
+
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       fd, err := os.Open(fileName)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       var parts []oss.UploadPart
+       for _, chunk := range chunks {
+               fd.Seek(chunk.Offset, os.SEEK_SET)
+               part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number, cryptoContext)
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       _, err = bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+
+       meta, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+       downfileName := "test-go-sdk-file-" + RandLowStr(5)
+       err = bucket.GetObjectToFile(objectName, downfileName)
+       c.Assert(err, IsNil)
+
+       downFileMD5, err := GetFileMD5(downfileName)
+       c.Assert(err, IsNil)
+       c.Assert(downFileMD5, Equals, srcMD5)
+
+       os.Remove(downfileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestMultipartUploadFromFile(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../../sample/BingWallpaper-2015-11-07.jpg"
+
+       srcMD5, err := GetFileMD5(fileName)
+       c.Assert(err, IsNil)
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       options := []oss.Option{oss.Meta("my", "myprop")}
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = (dataSize / 16 / 3) * 16
+       imur, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext, options...)
+       c.Assert(err, IsNil)
+
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       var parts []oss.UploadPart
+       for _, chunk := range chunks {
+               part, err := bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, chunk.Number, cryptoContext)
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       _, err = bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+
+       meta, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+       downfileName := "test-go-sdk-file-" + RandLowStr(5) + ".jpg"
+       err = bucket.GetObjectToFile(objectName, downfileName)
+       c.Assert(err, IsNil)
+
+       downFileMD5, err := GetFileMD5(downfileName)
+       c.Assert(err, IsNil)
+       c.Assert(downFileMD5, Equals, srcMD5)
+
+       os.Remove(downfileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestMultipartUploadFromSmallSizeFile(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "oss-go-sdk-test-file-" + RandStr(5)
+       fo, err := os.Create(fileName)
+       c.Assert(err, IsNil)
+       _, err = fo.Write([]byte("123"))
+       c.Assert(err, IsNil)
+       fo.Close()
+
+       srcMD5, err := GetFileMD5(fileName)
+       c.Assert(err, IsNil)
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       options := []oss.Option{oss.Meta("my", "myprop")}
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = 16
+       imur, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext, options...)
+       c.Assert(err, IsNil)
+
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       var parts []oss.UploadPart
+       for _, chunk := range chunks {
+               part, err := bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, chunk.Number, cryptoContext)
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       _, err = bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+
+       meta, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+       downfileName := "test-go-sdk-file-" + RandLowStr(5) + ".jpg"
+       err = bucket.GetObjectToFile(objectName, downfileName)
+       c.Assert(err, IsNil)
+
+       downFileMD5, err := GetFileMD5(downfileName)
+       c.Assert(err, IsNil)
+       c.Assert(downFileMD5, Equals, srcMD5)
+
+       os.Remove(fileName)
+       os.Remove(downfileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestListUploadedPartsNormal(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../../sample/BingWallpaper-2015-11-07.jpg"
+
+       srcMD5, err := GetFileMD5(fileName)
+       c.Assert(err, IsNil)
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       // Upload
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = (dataSize / 16 / 3) * 16
+       imurUpload, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext)
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       var partsUpload []oss.UploadPart
+       for _, chunk := range chunks {
+               part, err := bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number), cryptoContext)
+               c.Assert(err, IsNil)
+               partsUpload = append(partsUpload, part)
+       }
+
+       // List
+       lupr, err := bucket.ListUploadedParts(imurUpload)
+       c.Assert(err, IsNil)
+       c.Assert(len(lupr.UploadedParts), Equals, len(chunks))
+
+       // Complete
+       _, err = bucket.CompleteMultipartUpload(imurUpload, partsUpload)
+       c.Assert(err, IsNil)
+
+       // Download
+       downfileName := "test-go-sdk-file-" + RandLowStr(5) + ".jpg"
+       err = bucket.GetObjectToFile(objectName, downfileName)
+
+       downFileMD5, err := GetFileMD5(downfileName)
+       c.Assert(err, IsNil)
+       c.Assert(downFileMD5, Equals, srcMD5)
+
+       os.Remove(downfileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestListUploadedPartsComplete(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../../sample/BingWallpaper-2015-11-07.jpg"
+
+       srcMD5, err := GetFileMD5(fileName)
+       c.Assert(err, IsNil)
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       // Upload
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = (dataSize / 16 / 3) * 16
+       imurUpload, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext)
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       var partsUpload []oss.UploadPart
+       i := 0
+       // upload excepted the last part
+       for ; i < len(chunks)-1; i++ {
+               part, err := bucket.UploadPartFromFile(imurUpload, fileName, chunks[i].Offset, chunks[i].Size, (int)(chunks[i].Number), cryptoContext)
+               c.Assert(err, IsNil)
+               partsUpload = append(partsUpload, part)
+       }
+
+       // List
+       lupr, err := bucket.ListUploadedParts(imurUpload)
+       c.Assert(err, IsNil)
+       c.Assert(len(lupr.UploadedParts), Equals, len(chunks)-1)
+
+       lmur, err := bucket.ListMultipartUploads()
+       c.Assert(err, IsNil)
+       c.Assert(len(lmur.Uploads), Equals, 1)
+
+       // upload the last part with list part result
+       part, err := bucket.UploadPartFromFile(imurUpload, fileName, chunks[i].Offset, chunks[i].Size, (int)(chunks[i].Number), cryptoContext)
+       c.Assert(err, IsNil)
+       partsUpload = append(partsUpload, part)
+
+       // Complete
+       _, err = bucket.CompleteMultipartUpload(imurUpload, partsUpload)
+       c.Assert(err, IsNil)
+
+       // Download
+       downfileName := "test-go-sdk-file-" + RandLowStr(5) + ".jpg"
+       err = bucket.GetObjectToFile(objectName, downfileName)
+
+       downFileMD5, err := GetFileMD5(downfileName)
+       c.Assert(err, IsNil)
+       c.Assert(downFileMD5, Equals, srcMD5)
+
+       os.Remove(downfileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestListUploadedPartsAbortUseInit(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../../sample/BingWallpaper-2015-11-07.jpg"
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       // Upload
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = (dataSize / 16 / 3) * 16
+       imurUpload, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext)
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+       c.Assert(cryptoContext.Valid(), Equals, true)
+
+       var partsUpload []oss.UploadPart
+       i := 0
+       // upload excepted the last part
+       for ; i < len(chunks)-1; i++ {
+               part, err := bucket.UploadPartFromFile(imurUpload, fileName, chunks[i].Offset, chunks[i].Size, (int)(chunks[i].Number), cryptoContext)
+               c.Assert(err, IsNil)
+               partsUpload = append(partsUpload, part)
+       }
+
+       // List
+       lupr, err := bucket.ListUploadedParts(imurUpload)
+       c.Assert(err, IsNil)
+       c.Assert(len(lupr.UploadedParts), Equals, len(chunks)-1)
+
+       lmur, err := bucket.ListMultipartUploads()
+       c.Assert(err, IsNil)
+       c.Assert(len(lmur.Uploads), Equals, 1)
+
+       // abort upload
+       err = bucket.AbortMultipartUpload(imurUpload)
+       c.Assert(err, IsNil)
+
+       // list again
+       lupr, err = bucket.ListUploadedParts(imurUpload)
+       c.Assert(err, NotNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestListUploadedPartsAbortUseList(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../../sample/BingWallpaper-2015-11-07.jpg"
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       // Upload
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = (dataSize / 16 / 3) * 16
+       imurUpload, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext)
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       var partsUpload []oss.UploadPart
+       i := 0
+       // upload excepted the last part
+       for ; i < len(chunks)-1; i++ {
+               part, err := bucket.UploadPartFromFile(imurUpload, fileName, chunks[i].Offset, chunks[i].Size, (int)(chunks[i].Number), cryptoContext)
+               c.Assert(err, IsNil)
+               partsUpload = append(partsUpload, part)
+       }
+
+       // List
+       lupr, err := bucket.ListUploadedParts(imurUpload)
+       c.Assert(err, IsNil)
+       c.Assert(len(lupr.UploadedParts), Equals, len(chunks)-1)
+
+       // abort upload
+       err = bucket.AbortMultipartUpload(imurUpload)
+       c.Assert(err, IsNil)
+
+       // list again
+       lupr, err = bucket.ListUploadedParts(imurUpload)
+       c.Assert(err, NotNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestInitiateMultipartUpload(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       context := RandStr(ivSize * 1024 * 10)
+       fileName := "test-go-sdk-file-" + RandStr(5)
+
+       err = ioutil.WriteFile(fileName, []byte(context), 0666)
+       c.Assert(err, IsNil)
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = ivSize * 1024
+       imurUpload, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext)
+       c.Assert(err, IsNil)
+
+       err = bucket.AbortMultipartUpload(imurUpload)
+       c.Assert(err, IsNil)
+
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = ivSize / 2
+       imurUpload, err = bucket.InitiateMultipartUpload(objectName, &cryptoContext)
+       c.Assert(err, NotNil)
+
+       os.Remove(fileName)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestUploadPartError(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       context := RandStr(ivSize * 1024 * 10)
+       fileName := "test-go-sdk-file-" + RandStr(5)
+
+       err = ioutil.WriteFile(fileName, []byte(context), 0666)
+       c.Assert(err, IsNil)
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = ivSize * 1024
+       imur, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext)
+       c.Assert(err, IsNil)
+
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       fd, err := os.Open(fileName)
+       c.Assert(err, IsNil)
+       for _, chunk := range chunks {
+               fd.Seek(chunk.Offset, os.SEEK_SET)
+               _, err := bucket.UploadPart(imur, fd, chunk.Size+1, chunk.Number, cryptoContext)
+               c.Assert(err, NotNil)
+       }
+
+       for _, chunk := range chunks {
+               fd.Seek(chunk.Offset, os.SEEK_SET)
+               _, err := bucket.UploadPart(imur, fd, chunk.Size, 0, cryptoContext)
+               c.Assert(err, NotNil)
+       }
+       fd.Close()
+
+       err = bucket.AbortMultipartUpload(imur)
+       c.Assert(err, IsNil)
+       os.Remove(fileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestUploadPartFromFileError(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       context := RandStr(ivSize * 1024 * 10)
+       fileName := "test-go-sdk-file-" + RandStr(5)
+
+       err = ioutil.WriteFile(fileName, []byte(context), 0666)
+       c.Assert(err, IsNil)
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = ivSize * 1024
+       imur, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext)
+       c.Assert(err, IsNil)
+
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       for _, chunk := range chunks {
+               _, err := bucket.UploadPartFromFile(imur, fileName+".test", chunk.Offset, chunk.Size, chunk.Number, cryptoContext)
+               c.Assert(err, NotNil)
+       }
+
+       _, err = bucket.UploadPartFromFile(imur, fileName+".test", chunks[0].Offset, chunks[0].Size+1, chunks[0].Number, cryptoContext)
+       c.Assert(err, NotNil)
+
+       _, err = bucket.UploadPartFromFile(imur, fileName+".test", chunks[0].Offset, chunks[0].Size, 0, cryptoContext)
+       c.Assert(err, NotNil)
+
+       err = bucket.AbortMultipartUpload(imur)
+       c.Assert(err, IsNil)
+       os.Remove(fileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestUploadPartCopyError(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       context := RandStr(ivSize * 1024 * 10)
+       fileName := "test-go-sdk-file-" + RandStr(5)
+
+       err = ioutil.WriteFile(fileName, []byte(context), 0666)
+       c.Assert(err, IsNil)
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = ivSize * 1024
+       imur, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext)
+       c.Assert(err, IsNil)
+
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       _, err = bucket.UploadPartCopy(imur, bucketName, objectName, 0, chunks[0].Size, 1, cryptoContext)
+       c.Assert(err, NotNil)
+
+       err = bucket.AbortMultipartUpload(imur)
+       c.Assert(err, IsNil)
+       os.Remove(fileName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestMultipartUploadFromFileError(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../../sample/BingWallpaper-2015-11-07.jpg"
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       options := []oss.Option{oss.Meta("my", "myprop")}
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = -1
+       _, err = bucket.InitiateMultipartUpload(objectName, nil, options...)
+       c.Assert(err, NotNil)
+
+       _, err = bucket.InitiateMultipartUpload(objectName, &cryptoContext, options...)
+       c.Assert(err, NotNil)
+
+       cryptoContext.PartSize = (dataSize / 16 / 3) * 16
+       imur, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext, options...)
+       c.Assert(err, IsNil)
+
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       bakCC := cryptoContext.ContentCipher
+       cryptoContext.ContentCipher = nil
+
+       i := 0
+       // upload excepted the last part
+       for ; i < len(chunks); i++ {
+               _, err = bucket.UploadPartFromFile(imur, fileName, chunks[i].Offset, chunks[i].Size, (int)(chunks[i].Number), cryptoContext)
+               c.Assert(err, NotNil)
+
+       }
+
+       i = 0
+       cryptoContext.ContentCipher = bakCC
+       cryptoContext.PartSize -= 1
+       for ; i < len(chunks); i++ {
+               _, err = bucket.UploadPartFromFile(imur, fileName, chunks[i].Offset, chunks[i].Size, (int)(chunks[i].Number), cryptoContext)
+               c.Assert(err, NotNil)
+       }
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssCryptoBucketSuite) TestMultipartUploadPartError(c *C) {
+       // create a bucket with default proprety
+       client, err := oss.New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       // crypto bucket
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       contentProvider := CreateAesCtrCipher(masterRsaCipher)
+       bucket, err := GetCryptoBucket(client, bucketName, contentProvider)
+
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../../sample/BingWallpaper-2015-11-07.jpg"
+
+       fileInfo, err := os.Stat(fileName)
+       dataSize := fileInfo.Size()
+       c.Assert(err, IsNil)
+
+       options := []oss.Option{oss.Meta("my", "myprop")}
+       var cryptoContext PartCryptoContext
+       cryptoContext.DataSize = dataSize
+       cryptoContext.PartSize = (dataSize / 16 / 3) * 16
+       imur, err := bucket.InitiateMultipartUpload(objectName, &cryptoContext, options...)
+       c.Assert(err, IsNil)
+
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       c.Assert(err, IsNil)
+
+       bakCC := cryptoContext.ContentCipher
+       cryptoContext.ContentCipher = nil
+
+       fd, err := os.Open(fileName)
+       c.Assert(err, IsNil)
+
+       for _, chunk := range chunks {
+               fd.Seek(chunk.Offset, os.SEEK_SET)
+               _, err = bucket.UploadPart(imur, fd, chunk.Size, chunk.Number, cryptoContext)
+               c.Assert(err, NotNil)
+       }
+
+       cryptoContext.ContentCipher = bakCC
+       cryptoContext.PartSize -= 1
+       for _, chunk := range chunks {
+               _, err = bucket.UploadPart(imur, fd, chunk.Size, chunk.Number, cryptoContext)
+               c.Assert(err, NotNil)
+       }
+
+       ForceDeleteBucket(client, bucketName, c)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_type.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_type.go
new file mode 100644 (file)
index 0000000..c689327
--- /dev/null
@@ -0,0 +1,128 @@
+package osscrypto
+
+import (
+       "crypto/rand"
+       "encoding/binary"
+       "fmt"
+       "io"
+       math_rand "math/rand"
+       "time"
+)
+
+// MasterCipher encrypt or decrpt CipherData
+// support master key: rsa && ali kms
+type MasterCipher interface {
+       Encrypt([]byte) ([]byte, error)
+       Decrypt([]byte) ([]byte, error)
+       GetWrapAlgorithm() string
+       GetMatDesc() string
+}
+
+// ContentCipherBuilder is used to create ContentCipher for encryting object's data
+type ContentCipherBuilder interface {
+       ContentCipher() (ContentCipher, error)
+       ContentCipherEnv(Envelope) (ContentCipher, error)
+       GetMatDesc() string
+}
+
+// ContentCipher is used to encrypt or decrypt object's data
+type ContentCipher interface {
+       EncryptContent(io.Reader) (io.ReadCloser, error)
+       DecryptContent(io.Reader) (io.ReadCloser, error)
+       Clone(cd CipherData) (ContentCipher, error)
+       GetEncryptedLen(int64) int64
+       GetCipherData() *CipherData
+       GetAlignLen() int
+}
+
+// Envelope is stored in oss object's meta
+type Envelope struct {
+       IV                    string
+       CipherKey             string
+       MatDesc               string
+       WrapAlg               string
+       CEKAlg                string
+       UnencryptedMD5        string
+       UnencryptedContentLen string
+}
+
+func (el Envelope) IsValid() bool {
+       return len(el.IV) > 0 &&
+               len(el.CipherKey) > 0 &&
+               len(el.WrapAlg) > 0 &&
+               len(el.CEKAlg) > 0
+}
+
+func (el Envelope) String() string {
+       return fmt.Sprintf("IV=%s&CipherKey=%s&WrapAlg=%s&CEKAlg=%s", el.IV, el.CipherKey, el.WrapAlg, el.CEKAlg)
+}
+
+// CipherData is secret key information
+type CipherData struct {
+       IV            []byte
+       Key           []byte
+       MatDesc       string
+       WrapAlgorithm string
+       CEKAlgorithm  string
+       EncryptedIV   []byte
+       EncryptedKey  []byte
+}
+
+func (cd *CipherData) RandomKeyIv(keyLen int, ivLen int) error {
+       math_rand.Seed(time.Now().UnixNano())
+
+       // Key
+       cd.Key = make([]byte, keyLen)
+       if _, err := io.ReadFull(rand.Reader, cd.Key); err != nil {
+               return err
+       }
+
+       // sizeof uint64
+       if ivLen < 8 {
+               return fmt.Errorf("ivLen:%d less than 8", ivLen)
+       }
+
+       // IV:reserve 8 bytes
+       cd.IV = make([]byte, ivLen)
+       if _, err := io.ReadFull(rand.Reader, cd.IV[0:ivLen-8]); err != nil {
+               return err
+       }
+
+       // only use 4 byte,in order not to overflow when SeekIV()
+       randNumber := math_rand.Uint32()
+       cd.SetIV(uint64(randNumber))
+       return nil
+}
+
+func (cd *CipherData) SetIV(iv uint64) {
+       ivLen := len(cd.IV)
+       binary.BigEndian.PutUint64(cd.IV[ivLen-8:], iv)
+}
+
+func (cd *CipherData) GetIV() uint64 {
+       ivLen := len(cd.IV)
+       return binary.BigEndian.Uint64(cd.IV[ivLen-8:])
+}
+
+func (cd *CipherData) SeekIV(startPos uint64) {
+       cd.SetIV(cd.GetIV() + startPos/uint64(len(cd.IV)))
+}
+
+func (cd *CipherData) Clone() CipherData {
+       var cloneCd CipherData
+       cloneCd = *cd
+
+       cloneCd.Key = make([]byte, len(cd.Key))
+       copy(cloneCd.Key, cd.Key)
+
+       cloneCd.IV = make([]byte, len(cd.IV))
+       copy(cloneCd.IV, cd.IV)
+
+       cloneCd.EncryptedIV = make([]byte, len(cd.EncryptedIV))
+       copy(cloneCd.EncryptedIV, cd.EncryptedIV)
+
+       cloneCd.EncryptedKey = make([]byte, len(cd.EncryptedKey))
+       copy(cloneCd.EncryptedKey, cd.EncryptedKey)
+
+       return cloneCd
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_upload.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/crypto_upload.go
new file mode 100644 (file)
index 0000000..78be17d
--- /dev/null
@@ -0,0 +1,12 @@
+package osscrypto
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// UploadFile with multi part mode
+func (bucket CryptoBucket) UploadFile(objectKey, filePath string, partSize int64, options ...oss.Option) error {
+       return fmt.Errorf("CryptoBucket doesn't support UploadFile")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_alikms_cipher.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_alikms_cipher.go
new file mode 100644 (file)
index 0000000..3c55ff1
--- /dev/null
@@ -0,0 +1,85 @@
+package osscrypto
+
+import (
+       "encoding/base64"
+       "encoding/json"
+       "fmt"
+
+       kms "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
+)
+
+// CreateMasterAliKms Create master key interface implemented by ali kms
+// matDesc will be converted to json string
+func CreateMasterAliKms(matDesc map[string]string, kmsID string, kmsClient *kms.Client) (MasterCipher, error) {
+       var masterCipher MasterAliKmsCipher
+       if kmsID == "" || kmsClient == nil {
+               return masterCipher, fmt.Errorf("kmsID is empty or kmsClient is nil")
+       }
+
+       var jsonDesc string
+       if len(matDesc) > 0 {
+               b, err := json.Marshal(matDesc)
+               if err != nil {
+                       return masterCipher, err
+               }
+               jsonDesc = string(b)
+       }
+
+       masterCipher.MatDesc = jsonDesc
+       masterCipher.KmsID = kmsID
+       masterCipher.KmsClient = kmsClient
+       return masterCipher, nil
+}
+
+// MasterAliKmsCipher ali kms master key interface
+type MasterAliKmsCipher struct {
+       MatDesc   string
+       KmsID     string
+       KmsClient *kms.Client
+}
+
+// GetWrapAlgorithm get master key wrap algorithm
+func (mrc MasterAliKmsCipher) GetWrapAlgorithm() string {
+       return KmsAliCryptoWrap
+}
+
+// GetMatDesc get master key describe
+func (mkms MasterAliKmsCipher) GetMatDesc() string {
+       return mkms.MatDesc
+}
+
+// Encrypt  encrypt data by ali kms
+// Mainly used to encrypt object's symmetric secret key and iv
+func (mkms MasterAliKmsCipher) Encrypt(plainData []byte) ([]byte, error) {
+       // kms Plaintext must be base64 encoded
+       base64Plain := base64.StdEncoding.EncodeToString(plainData)
+       request := kms.CreateEncryptRequest()
+       request.RpcRequest.Scheme = "https"
+       request.RpcRequest.Method = "POST"
+       request.RpcRequest.AcceptFormat = "json"
+
+       request.KeyId = mkms.KmsID
+       request.Plaintext = base64Plain
+
+       response, err := mkms.KmsClient.Encrypt(request)
+       if err != nil {
+               return nil, err
+       }
+       return base64.StdEncoding.DecodeString(response.CiphertextBlob)
+}
+
+// Decrypt decrypt data by ali kms
+// Mainly used to decrypt object's symmetric secret key and iv
+func (mkms MasterAliKmsCipher) Decrypt(cryptoData []byte) ([]byte, error) {
+       base64Crypto := base64.StdEncoding.EncodeToString(cryptoData)
+       request := kms.CreateDecryptRequest()
+       request.RpcRequest.Scheme = "https"
+       request.RpcRequest.Method = "POST"
+       request.RpcRequest.AcceptFormat = "json"
+       request.CiphertextBlob = string(base64Crypto)
+       response, err := mkms.KmsClient.Decrypt(request)
+       if err != nil {
+               return nil, err
+       }
+       return base64.StdEncoding.DecodeString(response.Plaintext)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_alikms_cipher_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_alikms_cipher_test.go
new file mode 100644 (file)
index 0000000..70cc509
--- /dev/null
@@ -0,0 +1,89 @@
+package osscrypto
+
+import (
+       crypto_rand "crypto/rand"
+       "encoding/base64"
+       "io"
+       "math/rand"
+       "time"
+
+       kms "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
+       . "gopkg.in/check.v1"
+)
+
+func (s *OssCryptoBucketSuite) TestKmsClient(c *C) {
+       rand.Seed(time.Now().UnixNano())
+       kmsClient, err := kms.NewClientWithAccessKey(kmsRegion, kmsAccessID, kmsAccessKey)
+       c.Assert(err, IsNil)
+
+       // encrypte
+       enReq := kms.CreateEncryptRequest()
+       enReq.RpcRequest.Scheme = "https"
+       enReq.RpcRequest.Method = "POST"
+       enReq.RpcRequest.AcceptFormat = "json"
+
+       enReq.KeyId = kmsID
+
+       buff := make([]byte, 10)
+       _, err = io.ReadFull(crypto_rand.Reader, buff)
+       c.Assert(err, IsNil)
+       enReq.Plaintext = base64.StdEncoding.EncodeToString(buff)
+
+       enResponse, err := kmsClient.Encrypt(enReq)
+       c.Assert(err, IsNil)
+
+       // decrypte
+       deReq := kms.CreateDecryptRequest()
+       deReq.RpcRequest.Scheme = "https"
+       deReq.RpcRequest.Method = "POST"
+       deReq.RpcRequest.AcceptFormat = "json"
+       deReq.CiphertextBlob = enResponse.CiphertextBlob
+       deResponse, err := kmsClient.Decrypt(deReq)
+       c.Assert(err, IsNil)
+       c.Assert(deResponse.Plaintext, Equals, enReq.Plaintext)
+}
+
+func (s *OssCryptoBucketSuite) TestMasterAliKmsCipherSuccess(c *C) {
+
+       kmsClient, err := kms.NewClientWithAccessKey(kmsRegion, kmsAccessID, kmsAccessKey)
+       c.Assert(err, IsNil)
+
+       masterCipher, _ := CreateMasterAliKms(matDesc, kmsID, kmsClient)
+
+       var cd CipherData
+       err = cd.RandomKeyIv(aesKeySize, ivSize)
+       c.Assert(err, IsNil)
+
+       cd.WrapAlgorithm = masterCipher.GetWrapAlgorithm()
+       cd.CEKAlgorithm = KmsAliCryptoWrap
+       cd.MatDesc = masterCipher.GetMatDesc()
+
+       // EncryptedKey
+       cd.EncryptedKey, err = masterCipher.Encrypt(cd.Key)
+
+       // EncryptedIV
+       cd.EncryptedIV, err = masterCipher.Encrypt(cd.IV)
+
+       cloneData := cd.Clone()
+
+       cloneData.Key, _ = masterCipher.Decrypt(cloneData.EncryptedKey)
+       cloneData.IV, _ = masterCipher.Decrypt(cloneData.EncryptedIV)
+
+       c.Assert(string(cd.Key), Equals, string(cloneData.Key))
+       c.Assert(string(cd.IV), Equals, string(cloneData.IV))
+
+}
+
+func (s *OssCryptoBucketSuite) TestMasterAliKmsCipherError(c *C) {
+       kmsClient, err := kms.NewClientWithAccessKey(kmsRegion, kmsAccessID, kmsAccessKey)
+       c.Assert(err, IsNil)
+
+       masterCipher, _ := CreateMasterAliKms(matDesc, kmsID, kmsClient)
+       v := masterCipher.(MasterAliKmsCipher)
+       v.KmsID = ""
+       _, err = v.Encrypt([]byte("hellow"))
+       c.Assert(err, NotNil)
+
+       _, err = v.Decrypt([]byte("hellow"))
+       c.Assert(err, NotNil)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_rsa_cipher.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_rsa_cipher.go
new file mode 100644 (file)
index 0000000..616f3e1
--- /dev/null
@@ -0,0 +1,102 @@
+package osscrypto
+
+import (
+       "crypto/rand"
+       "crypto/rsa"
+       "crypto/x509"
+       "encoding/asn1"
+       "encoding/json"
+       "encoding/pem"
+       "fmt"
+)
+
+// CreateMasterRsa Create master key interface implemented by rsa
+// matDesc will be converted to json string
+func CreateMasterRsa(matDesc map[string]string, publicKey string, privateKey string) (MasterCipher, error) {
+       var masterCipher MasterRsaCipher
+       var jsonDesc string
+       if len(matDesc) > 0 {
+               b, err := json.Marshal(matDesc)
+               if err != nil {
+                       return masterCipher, err
+               }
+               jsonDesc = string(b)
+       }
+       masterCipher.MatDesc = jsonDesc
+       masterCipher.PublicKey = publicKey
+       masterCipher.PrivateKey = privateKey
+       return masterCipher, nil
+}
+
+// MasterRsaCipher rsa master key interface
+type MasterRsaCipher struct {
+       MatDesc    string
+       PublicKey  string
+       PrivateKey string
+}
+
+// GetWrapAlgorithm get master key wrap algorithm
+func (mrc MasterRsaCipher) GetWrapAlgorithm() string {
+       return RsaCryptoWrap
+}
+
+// GetMatDesc get master key describe
+func (mrc MasterRsaCipher) GetMatDesc() string {
+       return mrc.MatDesc
+}
+
+// Encrypt encrypt data by rsa public key
+// Mainly used to encrypt object's symmetric secret key and iv
+func (mrc MasterRsaCipher) Encrypt(plainData []byte) ([]byte, error) {
+       block, _ := pem.Decode([]byte(mrc.PublicKey))
+       if block == nil {
+               return nil, fmt.Errorf("pem.Decode public key error")
+       }
+
+       var pub *rsa.PublicKey
+       if block.Type == "PUBLIC KEY" {
+               // pks8 format
+               pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
+               if err != nil {
+                       return nil, err
+               }
+               pub = pubInterface.(*rsa.PublicKey)
+       } else if block.Type == "RSA PUBLIC KEY" {
+               // pks1 format
+               pub = &rsa.PublicKey{}
+               _, err := asn1.Unmarshal(block.Bytes, pub)
+               if err != nil {
+                       return nil, err
+               }
+       } else {
+               return nil, fmt.Errorf("not supported public key,type:%s", block.Type)
+       }
+       return rsa.EncryptPKCS1v15(rand.Reader, pub, plainData)
+}
+
+// Decrypt Decrypt data by rsa private key
+// Mainly used to decrypt object's symmetric secret key and iv
+func (mrc MasterRsaCipher) Decrypt(cryptoData []byte) ([]byte, error) {
+       block, _ := pem.Decode([]byte(mrc.PrivateKey))
+       if block == nil {
+               return nil, fmt.Errorf("pem.Decode private key error")
+       }
+
+       if block.Type == "PRIVATE KEY" {
+               // pks8 format
+               privInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes)
+               if err != nil {
+                       return nil, err
+               }
+               return rsa.DecryptPKCS1v15(rand.Reader, privInterface.(*rsa.PrivateKey), cryptoData)
+       } else if block.Type == "RSA PRIVATE KEY" {
+               // pks1 format
+               priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+               if err != nil {
+                       return nil, err
+               }
+               return rsa.DecryptPKCS1v15(rand.Reader, priv, cryptoData)
+       } else {
+               return nil, fmt.Errorf("not supported private key,type:%s", block.Type)
+       }
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_rsa_cipher_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crypto/master_rsa_cipher_test.go
new file mode 100644 (file)
index 0000000..dca5adb
--- /dev/null
@@ -0,0 +1,41 @@
+package osscrypto
+
+import (
+       "strings"
+
+       . "gopkg.in/check.v1"
+)
+
+func (s *OssCryptoBucketSuite) TestMasterRsaError(c *C) {
+
+       masterRsaCipher, _ := CreateMasterRsa(matDesc, RandLowStr(100), rsaPrivateKey)
+       _, err := masterRsaCipher.Encrypt([]byte("123"))
+       c.Assert(err, NotNil)
+
+       masterRsaCipher, _ = CreateMasterRsa(matDesc, rsaPublicKey, RandLowStr(100))
+       _, err = masterRsaCipher.Decrypt([]byte("123"))
+       c.Assert(err, NotNil)
+
+       testPrivateKey := rsaPrivateKey
+       []byte(testPrivateKey)[100] = testPrivateKey[90]
+       masterRsaCipher, _ = CreateMasterRsa(matDesc, rsaPublicKey, testPrivateKey)
+       _, err = masterRsaCipher.Decrypt([]byte("123"))
+       c.Assert(err, NotNil)
+
+       masterRsaCipher, _ = CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+
+       var cipherData CipherData
+       err = cipherData.RandomKeyIv(aesKeySize/2, ivSize/4)
+       c.Assert(err, NotNil)
+
+       masterRsaCipher, _ = CreateMasterRsa(matDesc, rsaPublicKey, rsaPrivateKey)
+       v := masterRsaCipher.(MasterRsaCipher)
+
+       v.PublicKey = strings.Replace(rsaPublicKey, "PUBLIC KEY", "CERTIFICATE", -1)
+       _, err = v.Encrypt([]byte("HELLOW"))
+       c.Assert(err, NotNil)
+
+       v.PrivateKey = strings.Replace(rsaPrivateKey, "PRIVATE KEY", "CERTIFICATE", -1)
+       _, err = v.Decrypt([]byte("HELLOW"))
+       c.Assert(err, NotNil)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download.go
new file mode 100644 (file)
index 0000000..90c1b63
--- /dev/null
@@ -0,0 +1,567 @@
+package oss
+
+import (
+       "crypto/md5"
+       "encoding/base64"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "hash"
+       "hash/crc64"
+       "io"
+       "io/ioutil"
+       "net/http"
+       "os"
+       "path/filepath"
+       "strconv"
+       "time"
+)
+
+// DownloadFile downloads files with multipart download.
+//
+// objectKey    the object key.
+// filePath    the local file to download from objectKey in OSS.
+// partSize    the part size in bytes.
+// options    object's constraints, check out GetObject for the reference.
+//
+// error    it's nil when the call succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) DownloadFile(objectKey, filePath string, partSize int64, options ...Option) error {
+       if partSize < 1 {
+               return errors.New("oss: part size smaller than 1")
+       }
+
+       uRange, err := GetRangeConfig(options)
+       if err != nil {
+               return err
+       }
+
+       cpConf := getCpConfig(options)
+       routines := getRoutines(options)
+
+       var strVersionId string
+       versionId, _ := FindOption(options, "versionId", nil)
+       if versionId != nil {
+               strVersionId = versionId.(string)
+       }
+
+       if cpConf != nil && cpConf.IsEnable {
+               cpFilePath := getDownloadCpFilePath(cpConf, bucket.BucketName, objectKey, strVersionId, filePath)
+               if cpFilePath != "" {
+                       return bucket.downloadFileWithCp(objectKey, filePath, partSize, options, cpFilePath, routines, uRange)
+               }
+       }
+
+       return bucket.downloadFile(objectKey, filePath, partSize, options, routines, uRange)
+}
+
+func getDownloadCpFilePath(cpConf *cpConfig, srcBucket, srcObject, versionId, destFile string) string {
+       if cpConf.FilePath == "" && cpConf.DirPath != "" {
+               src := fmt.Sprintf("oss://%v/%v", srcBucket, srcObject)
+               absPath, _ := filepath.Abs(destFile)
+               cpFileName := getCpFileName(src, absPath, versionId)
+               cpConf.FilePath = cpConf.DirPath + string(os.PathSeparator) + cpFileName
+       }
+       return cpConf.FilePath
+}
+
+// downloadWorkerArg is download worker's parameters
+type downloadWorkerArg struct {
+       bucket    *Bucket
+       key       string
+       filePath  string
+       options   []Option
+       hook      downloadPartHook
+       enableCRC bool
+}
+
+// downloadPartHook is hook for test
+type downloadPartHook func(part downloadPart) error
+
+var downloadPartHooker downloadPartHook = defaultDownloadPartHook
+
+func defaultDownloadPartHook(part downloadPart) error {
+       return nil
+}
+
+// defaultDownloadProgressListener defines default ProgressListener, shields the ProgressListener in options of GetObject.
+type defaultDownloadProgressListener struct {
+}
+
+// ProgressChanged no-ops
+func (listener *defaultDownloadProgressListener) ProgressChanged(event *ProgressEvent) {
+}
+
+// downloadWorker
+func downloadWorker(id int, arg downloadWorkerArg, jobs <-chan downloadPart, results chan<- downloadPart, failed chan<- error, die <-chan bool) {
+       for part := range jobs {
+               if err := arg.hook(part); err != nil {
+                       failed <- err
+                       break
+               }
+
+               // Resolve options
+               r := Range(part.Start, part.End)
+               p := Progress(&defaultDownloadProgressListener{})
+
+               var respHeader http.Header
+               opts := make([]Option, len(arg.options)+3)
+               // Append orderly, can not be reversed!
+               opts = append(opts, arg.options...)
+               opts = append(opts, r, p, GetResponseHeader(&respHeader))
+
+               rd, err := arg.bucket.GetObject(arg.key, opts...)
+               if err != nil {
+                       failed <- err
+                       break
+               }
+               defer rd.Close()
+
+               var crcCalc hash.Hash64
+               if arg.enableCRC {
+                       crcCalc = crc64.New(CrcTable())
+                       contentLen := part.End - part.Start + 1
+                       rd = ioutil.NopCloser(TeeReader(rd, crcCalc, contentLen, nil, nil))
+               }
+               defer rd.Close()
+
+               select {
+               case <-die:
+                       return
+               default:
+               }
+
+               fd, err := os.OpenFile(arg.filePath, os.O_WRONLY, FilePermMode)
+               if err != nil {
+                       failed <- err
+                       break
+               }
+
+               _, err = fd.Seek(part.Start-part.Offset, os.SEEK_SET)
+               if err != nil {
+                       fd.Close()
+                       failed <- err
+                       break
+               }
+
+               startT := time.Now().UnixNano() / 1000 / 1000 / 1000
+               _, err = io.Copy(fd, rd)
+               endT := time.Now().UnixNano() / 1000 / 1000 / 1000
+               if err != nil {
+                       arg.bucket.Client.Config.WriteLog(Debug, "download part error,cost:%d second,part number:%d,request id:%s,error:%s.\n", endT-startT, part.Index, GetRequestId(respHeader), err.Error())
+                       fd.Close()
+                       failed <- err
+                       break
+               }
+
+               if arg.enableCRC {
+                       part.CRC64 = crcCalc.Sum64()
+               }
+
+               fd.Close()
+               results <- part
+       }
+}
+
+// downloadScheduler
+func downloadScheduler(jobs chan downloadPart, parts []downloadPart) {
+       for _, part := range parts {
+               jobs <- part
+       }
+       close(jobs)
+}
+
+// downloadPart defines download part
+type downloadPart struct {
+       Index  int    // Part number, starting from 0
+       Start  int64  // Start index
+       End    int64  // End index
+       Offset int64  // Offset
+       CRC64  uint64 // CRC check value of part
+}
+
+// getDownloadParts gets download parts
+func getDownloadParts(objectSize, partSize int64, uRange *UnpackedRange) []downloadPart {
+       parts := []downloadPart{}
+       part := downloadPart{}
+       i := 0
+       start, end := AdjustRange(uRange, objectSize)
+       for offset := start; offset < end; offset += partSize {
+               part.Index = i
+               part.Start = offset
+               part.End = GetPartEnd(offset, end, partSize)
+               part.Offset = start
+               part.CRC64 = 0
+               parts = append(parts, part)
+               i++
+       }
+       return parts
+}
+
+// getObjectBytes gets object bytes length
+func getObjectBytes(parts []downloadPart) int64 {
+       var ob int64
+       for _, part := range parts {
+               ob += (part.End - part.Start + 1)
+       }
+       return ob
+}
+
+// combineCRCInParts caculates the total CRC of continuous parts
+func combineCRCInParts(dps []downloadPart) uint64 {
+       if dps == nil || len(dps) == 0 {
+               return 0
+       }
+
+       crc := dps[0].CRC64
+       for i := 1; i < len(dps); i++ {
+               crc = CRC64Combine(crc, dps[i].CRC64, (uint64)(dps[i].End-dps[i].Start+1))
+       }
+
+       return crc
+}
+
+// downloadFile downloads file concurrently without checkpoint.
+func (bucket Bucket) downloadFile(objectKey, filePath string, partSize int64, options []Option, routines int, uRange *UnpackedRange) error {
+       tempFilePath := filePath + TempFileSuffix
+       listener := GetProgressListener(options)
+
+       // If the file does not exist, create one. If exists, the download will overwrite it.
+       fd, err := os.OpenFile(tempFilePath, os.O_WRONLY|os.O_CREATE, FilePermMode)
+       if err != nil {
+               return err
+       }
+       fd.Close()
+
+       // Get the object detailed meta for object whole size
+       // must delete header:range to get whole object size
+       skipOptions := DeleteOption(options, HTTPHeaderRange)
+       meta, err := bucket.GetObjectDetailedMeta(objectKey, skipOptions...)
+       if err != nil {
+               return err
+       }
+
+       objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 64)
+       if err != nil {
+               return err
+       }
+
+       enableCRC := false
+       expectedCRC := (uint64)(0)
+       if bucket.GetConfig().IsEnableCRC && meta.Get(HTTPHeaderOssCRC64) != "" {
+               if uRange == nil || (!uRange.HasStart && !uRange.HasEnd) {
+                       enableCRC = true
+                       expectedCRC, _ = strconv.ParseUint(meta.Get(HTTPHeaderOssCRC64), 10, 64)
+               }
+       }
+
+       // Get the parts of the file
+       parts := getDownloadParts(objectSize, partSize, uRange)
+       jobs := make(chan downloadPart, len(parts))
+       results := make(chan downloadPart, len(parts))
+       failed := make(chan error)
+       die := make(chan bool)
+
+       var completedBytes int64
+       totalBytes := getObjectBytes(parts)
+       event := newProgressEvent(TransferStartedEvent, 0, totalBytes, 0)
+       publishProgress(listener, event)
+
+       // Start the download workers
+       arg := downloadWorkerArg{&bucket, objectKey, tempFilePath, options, downloadPartHooker, enableCRC}
+       for w := 1; w <= routines; w++ {
+               go downloadWorker(w, arg, jobs, results, failed, die)
+       }
+
+       // Download parts concurrently
+       go downloadScheduler(jobs, parts)
+
+       // Waiting for parts download finished
+       completed := 0
+       for completed < len(parts) {
+               select {
+               case part := <-results:
+                       completed++
+                       downBytes := (part.End - part.Start + 1)
+                       completedBytes += downBytes
+                       parts[part.Index].CRC64 = part.CRC64
+                       event = newProgressEvent(TransferDataEvent, completedBytes, totalBytes, downBytes)
+                       publishProgress(listener, event)
+               case err := <-failed:
+                       close(die)
+                       event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes, 0)
+                       publishProgress(listener, event)
+                       return err
+               }
+
+               if completed >= len(parts) {
+                       break
+               }
+       }
+
+       event = newProgressEvent(TransferCompletedEvent, completedBytes, totalBytes, 0)
+       publishProgress(listener, event)
+
+       if enableCRC {
+               actualCRC := combineCRCInParts(parts)
+               err = CheckDownloadCRC(actualCRC, expectedCRC)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return os.Rename(tempFilePath, filePath)
+}
+
+// ----- Concurrent download with chcekpoint  -----
+
+const downloadCpMagic = "92611BED-89E2-46B6-89E5-72F273D4B0A3"
+
+type downloadCheckpoint struct {
+       Magic     string         // Magic
+       MD5       string         // Checkpoint content MD5
+       FilePath  string         // Local file
+       Object    string         // Key
+       ObjStat   objectStat     // Object status
+       Parts     []downloadPart // All download parts
+       PartStat  []bool         // Parts' download status
+       Start     int64          // Start point of the file
+       End       int64          // End point of the file
+       enableCRC bool           // Whether has CRC check
+       CRC       uint64         // CRC check value
+}
+
+type objectStat struct {
+       Size         int64  // Object size
+       LastModified string // Last modified time
+       Etag         string // Etag
+}
+
+// isValid flags of checkpoint data is valid. It returns true when the data is valid and the checkpoint is valid and the object is not updated.
+func (cp downloadCheckpoint) isValid(meta http.Header, uRange *UnpackedRange) (bool, error) {
+       // Compare the CP's Magic and the MD5
+       cpb := cp
+       cpb.MD5 = ""
+       js, _ := json.Marshal(cpb)
+       sum := md5.Sum(js)
+       b64 := base64.StdEncoding.EncodeToString(sum[:])
+
+       if cp.Magic != downloadCpMagic || b64 != cp.MD5 {
+               return false, nil
+       }
+
+       objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 64)
+       if err != nil {
+               return false, err
+       }
+
+       // Compare the object size, last modified time and etag
+       if cp.ObjStat.Size != objectSize ||
+               cp.ObjStat.LastModified != meta.Get(HTTPHeaderLastModified) ||
+               cp.ObjStat.Etag != meta.Get(HTTPHeaderEtag) {
+               return false, nil
+       }
+
+       // Check the download range
+       if uRange != nil {
+               start, end := AdjustRange(uRange, objectSize)
+               if start != cp.Start || end != cp.End {
+                       return false, nil
+               }
+       }
+
+       return true, nil
+}
+
+// load checkpoint from local file
+func (cp *downloadCheckpoint) load(filePath string) error {
+       contents, err := ioutil.ReadFile(filePath)
+       if err != nil {
+               return err
+       }
+
+       err = json.Unmarshal(contents, cp)
+       return err
+}
+
+// dump funciton dumps to file
+func (cp *downloadCheckpoint) dump(filePath string) error {
+       bcp := *cp
+
+       // Calculate MD5
+       bcp.MD5 = ""
+       js, err := json.Marshal(bcp)
+       if err != nil {
+               return err
+       }
+       sum := md5.Sum(js)
+       b64 := base64.StdEncoding.EncodeToString(sum[:])
+       bcp.MD5 = b64
+
+       // Serialize
+       js, err = json.Marshal(bcp)
+       if err != nil {
+               return err
+       }
+
+       // Dump
+       return ioutil.WriteFile(filePath, js, FilePermMode)
+}
+
+// todoParts gets unfinished parts
+func (cp downloadCheckpoint) todoParts() []downloadPart {
+       dps := []downloadPart{}
+       for i, ps := range cp.PartStat {
+               if !ps {
+                       dps = append(dps, cp.Parts[i])
+               }
+       }
+       return dps
+}
+
+// getCompletedBytes gets completed size
+func (cp downloadCheckpoint) getCompletedBytes() int64 {
+       var completedBytes int64
+       for i, part := range cp.Parts {
+               if cp.PartStat[i] {
+                       completedBytes += (part.End - part.Start + 1)
+               }
+       }
+       return completedBytes
+}
+
+// prepare initiates download tasks
+func (cp *downloadCheckpoint) prepare(meta http.Header, bucket *Bucket, objectKey, filePath string, partSize int64, uRange *UnpackedRange) error {
+       // CP
+       cp.Magic = downloadCpMagic
+       cp.FilePath = filePath
+       cp.Object = objectKey
+
+       objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 64)
+       if err != nil {
+               return err
+       }
+
+       cp.ObjStat.Size = objectSize
+       cp.ObjStat.LastModified = meta.Get(HTTPHeaderLastModified)
+       cp.ObjStat.Etag = meta.Get(HTTPHeaderEtag)
+
+       if bucket.GetConfig().IsEnableCRC && meta.Get(HTTPHeaderOssCRC64) != "" {
+               if uRange == nil || (!uRange.HasStart && !uRange.HasEnd) {
+                       cp.enableCRC = true
+                       cp.CRC, _ = strconv.ParseUint(meta.Get(HTTPHeaderOssCRC64), 10, 64)
+               }
+       }
+
+       // Parts
+       cp.Parts = getDownloadParts(objectSize, partSize, uRange)
+       cp.PartStat = make([]bool, len(cp.Parts))
+       for i := range cp.PartStat {
+               cp.PartStat[i] = false
+       }
+
+       return nil
+}
+
+func (cp *downloadCheckpoint) complete(cpFilePath, downFilepath string) error {
+       err := os.Rename(downFilepath, cp.FilePath)
+       if err != nil {
+               return err
+       }
+       return os.Remove(cpFilePath)
+}
+
+// downloadFileWithCp downloads files with checkpoint.
+func (bucket Bucket) downloadFileWithCp(objectKey, filePath string, partSize int64, options []Option, cpFilePath string, routines int, uRange *UnpackedRange) error {
+       tempFilePath := filePath + TempFileSuffix
+       listener := GetProgressListener(options)
+
+       // Load checkpoint data.
+       dcp := downloadCheckpoint{}
+       err := dcp.load(cpFilePath)
+       if err != nil {
+               os.Remove(cpFilePath)
+       }
+
+       // Get the object detailed meta for object whole size
+       // must delete header:range to get whole object size
+       skipOptions := DeleteOption(options, HTTPHeaderRange)
+       meta, err := bucket.GetObjectDetailedMeta(objectKey, skipOptions...)
+       if err != nil {
+               return err
+       }
+
+       // Load error or data invalid. Re-initialize the download.
+       valid, err := dcp.isValid(meta, uRange)
+       if err != nil || !valid {
+               if err = dcp.prepare(meta, &bucket, objectKey, filePath, partSize, uRange); err != nil {
+                       return err
+               }
+               os.Remove(cpFilePath)
+       }
+
+       // Create the file if not exists. Otherwise the parts download will overwrite it.
+       fd, err := os.OpenFile(tempFilePath, os.O_WRONLY|os.O_CREATE, FilePermMode)
+       if err != nil {
+               return err
+       }
+       fd.Close()
+
+       // Unfinished parts
+       parts := dcp.todoParts()
+       jobs := make(chan downloadPart, len(parts))
+       results := make(chan downloadPart, len(parts))
+       failed := make(chan error)
+       die := make(chan bool)
+
+       completedBytes := dcp.getCompletedBytes()
+       event := newProgressEvent(TransferStartedEvent, completedBytes, dcp.ObjStat.Size, 0)
+       publishProgress(listener, event)
+
+       // Start the download workers routine
+       arg := downloadWorkerArg{&bucket, objectKey, tempFilePath, options, downloadPartHooker, dcp.enableCRC}
+       for w := 1; w <= routines; w++ {
+               go downloadWorker(w, arg, jobs, results, failed, die)
+       }
+
+       // Concurrently downloads parts
+       go downloadScheduler(jobs, parts)
+
+       // Wait for the parts download finished
+       completed := 0
+       for completed < len(parts) {
+               select {
+               case part := <-results:
+                       completed++
+                       dcp.PartStat[part.Index] = true
+                       dcp.Parts[part.Index].CRC64 = part.CRC64
+                       dcp.dump(cpFilePath)
+                       downBytes := (part.End - part.Start + 1)
+                       completedBytes += downBytes
+                       event = newProgressEvent(TransferDataEvent, completedBytes, dcp.ObjStat.Size, downBytes)
+                       publishProgress(listener, event)
+               case err := <-failed:
+                       close(die)
+                       event = newProgressEvent(TransferFailedEvent, completedBytes, dcp.ObjStat.Size, 0)
+                       publishProgress(listener, event)
+                       return err
+               }
+
+               if completed >= len(parts) {
+                       break
+               }
+       }
+
+       event = newProgressEvent(TransferCompletedEvent, completedBytes, dcp.ObjStat.Size, 0)
+       publishProgress(listener, event)
+
+       if dcp.enableCRC {
+               actualCRC := combineCRCInParts(dcp.Parts)
+               err = CheckDownloadCRC(actualCRC, dcp.CRC)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return dcp.complete(cpFilePath, tempFilePath)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download_test.go
new file mode 100644 (file)
index 0000000..6c73764
--- /dev/null
@@ -0,0 +1,979 @@
+package oss
+
+import (
+       "bytes"
+       "fmt"
+       "net/http"
+       "os"
+       "strings"
+       "time"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssDownloadSuite struct {
+       client *Client
+       bucket *Bucket
+}
+
+var _ = Suite(&OssDownloadSuite{})
+
+// SetUpSuite runs once when the suite starts running
+func (s *OssDownloadSuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       s.client = client
+
+       s.client.CreateBucket(bucketName)
+
+       bucket, err := s.client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       s.bucket = bucket
+
+       testLogger.Println("test download started")
+}
+
+// TearDownSuite runs before each test or benchmark starts running
+func (s *OssDownloadSuite) TearDownSuite(c *C) {
+       // Delete part
+       keyMarker := KeyMarker("")
+       uploadIDMarker := UploadIDMarker("")
+       for {
+               lmur, err := s.bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+               c.Assert(err, IsNil)
+               for _, upload := range lmur.Uploads {
+                       var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName,
+                               Key: upload.Key, UploadID: upload.UploadID}
+                       err = s.bucket.AbortMultipartUpload(imur)
+                       c.Assert(err, IsNil)
+               }
+               keyMarker = KeyMarker(lmur.NextKeyMarker)
+               uploadIDMarker = UploadIDMarker(lmur.NextUploadIDMarker)
+               if !lmur.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete objects
+       marker := Marker("")
+       for {
+               lor, err := s.bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = s.bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete bucket
+       err := s.client.DeleteBucket(s.bucket.BucketName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("test download completed")
+}
+
+// SetUpTest runs after each test or benchmark runs
+func (s *OssDownloadSuite) SetUpTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+}
+
+// TearDownTest runs once after all tests or benchmarks have finished running
+func (s *OssDownloadSuite) TearDownTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".temp")
+       c.Assert(err, IsNil)
+}
+
+// TestDownloadRoutineWithoutRecovery multipart downloads without checkpoint
+func (s *OssDownloadSuite) TestDownloadRoutineWithoutRecovery(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       // Upload a file
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       // Download the file by default
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024)
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Use 2 coroutines to download the file and total parts count is 5
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(2))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Use 5 coroutines to download the file and the total parts count is 5.
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(5))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Use 10 coroutines to download the file and the total parts count is 5.
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(10))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// DownErrorHooker requests hook by downloadPart
+func DownErrorHooker(part downloadPart) error {
+       if part.Index == 4 {
+               time.Sleep(time.Second)
+               return fmt.Errorf("ErrorHooker")
+       }
+       return nil
+}
+
+// TestDownloadRoutineWithRecovery multi-routine resumable download
+func (s *OssDownloadSuite) TestDownloadRoutineWithRecovery(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       // Upload a file
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       // Download a file with default checkpoint
+       downloadPartHooker = DownErrorHooker
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, newFile+".cp"))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       downloadPartHooker = defaultDownloadPartHook
+
+       // Check
+       dcp := downloadCheckpoint{}
+       err = dcp.load(newFile + ".cp")
+       c.Assert(err, IsNil)
+       c.Assert(dcp.Magic, Equals, downloadCpMagic)
+       c.Assert(len(dcp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ=="))
+       c.Assert(dcp.FilePath, Equals, newFile)
+       c.Assert(dcp.ObjStat.Size, Equals, int64(482048))
+       c.Assert(len(dcp.ObjStat.LastModified) > 0, Equals, true)
+       c.Assert(dcp.ObjStat.Etag, Equals, "\"2351E662233817A7AE974D8C5B0876DD-5\"")
+       c.Assert(dcp.Object, Equals, objectName)
+       c.Assert(len(dcp.Parts), Equals, 5)
+       c.Assert(len(dcp.todoParts()), Equals, 1)
+
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, newFile+".cp"))
+       c.Assert(err, IsNil)
+       //download success, checkpoint file has been deleted
+       err = dcp.load(newFile + ".cp")
+       c.Assert(err, NotNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Resumable download with empty checkpoint file path
+       downloadPartHooker = DownErrorHooker
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, ""))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       downloadPartHooker = defaultDownloadPartHook
+
+       dcp = downloadCheckpoint{}
+       err = dcp.load(newFile + ".cp")
+       c.Assert(err, NotNil)
+
+       // Resumable download with checkpoint dir
+       os.Remove(newFile)
+       downloadPartHooker = DownErrorHooker
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, CheckpointDir(true, "./"))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       downloadPartHooker = defaultDownloadPartHook
+
+       // Check
+       dcp = downloadCheckpoint{}
+       cpConf := cpConfig{IsEnable: true, DirPath: "./"}
+       cpFilePath := getDownloadCpFilePath(&cpConf, s.bucket.BucketName, objectName, "",newFile)
+       err = dcp.load(cpFilePath)
+       c.Assert(err, IsNil)
+       c.Assert(dcp.Magic, Equals, downloadCpMagic)
+       c.Assert(len(dcp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ=="))
+       c.Assert(dcp.FilePath, Equals, newFile)
+       c.Assert(dcp.ObjStat.Size, Equals, int64(482048))
+       c.Assert(len(dcp.ObjStat.LastModified) > 0, Equals, true)
+       c.Assert(dcp.ObjStat.Etag, Equals, "\"2351E662233817A7AE974D8C5B0876DD-5\"")
+       c.Assert(dcp.Object, Equals, objectName)
+       c.Assert(len(dcp.Parts), Equals, 5)
+       c.Assert(len(dcp.todoParts()), Equals, 1)
+
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, CheckpointDir(true, "./"))
+       c.Assert(err, IsNil)
+       //download success, checkpoint file has been deleted
+       err = dcp.load(cpFilePath)
+       c.Assert(err, NotNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Resumable download with checkpoint at a time. No error is expected in the download procedure.
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, newFile+".cp"))
+       c.Assert(err, IsNil)
+
+       err = dcp.load(newFile + ".cp")
+       c.Assert(err, NotNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Resumable download with checkpoint at a time. No error is expected in the download procedure.
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(10), Checkpoint(true, newFile+".cp"))
+       c.Assert(err, IsNil)
+
+       err = dcp.load(newFile + ".cp")
+       c.Assert(err, NotNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestDownloadOption options
+func (s *OssDownloadSuite) TestDownloadOption(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       // Upload the file
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+
+       // IfMatch
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), IfMatch(meta.Get("Etag")))
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // IfNoneMatch
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), IfNoneMatch(meta.Get("Etag")))
+       c.Assert(err, NotNil)
+
+       // IfMatch
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), IfMatch(meta.Get("Etag")))
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // IfNoneMatch
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), IfNoneMatch(meta.Get("Etag")))
+       c.Assert(err, NotNil)
+}
+
+// TestDownloadObjectChange tests the file is updated during the upload
+func (s *OssDownloadSuite) TestDownloadObjectChange(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       // Upload a file
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       // Download with default checkpoint
+       downloadPartHooker = DownErrorHooker
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, newFile+".cp"))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       downloadPartHooker = defaultDownloadPartHook
+
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, newFile+".cp"))
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+}
+
+// TestDownloadNegative tests downloading negative
+func (s *OssDownloadSuite) TestDownloadNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       // Upload a file
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       // Worker routine error
+       downloadPartHooker = DownErrorHooker
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(2))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       downloadPartHooker = defaultDownloadPartHook
+
+       // Local file does not exist
+       err = s.bucket.DownloadFile(objectName, "/tmp/", 100*1024, Routines(2))
+       c.Assert(err, NotNil)
+
+       // Invalid part size
+       err = s.bucket.DownloadFile(objectName, newFile, 0, Routines(2))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DownloadFile(objectName, newFile, 1024*1024*1024*100, Routines(2))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Local file does not exist
+       err = s.bucket.DownloadFile(objectName, "/tmp/", 100*1024)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DownloadFile(objectName, "/tmp/", 100*1024, Routines(2))
+       c.Assert(err, NotNil)
+
+       // Invalid part size
+       err = s.bucket.DownloadFile(objectName, newFile, -1)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DownloadFile(objectName, newFile, 0, Routines(2))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DownloadFile(objectName, newFile, 1024*1024*1024*100)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DownloadFile(objectName, newFile, 1024*1024*1024*100, Routines(2))
+       c.Assert(err, NotNil)
+}
+
+// TestDownloadWithRange tests concurrent downloading with range specified and checkpoint enabled
+func (s *OssDownloadSuite) TestDownloadWithRange(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+       newFileGet := RandStr(8) + "-.jpg"
+
+       // Upload a file
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       fileSize, err := getFileSize(fileName)
+       c.Assert(err, IsNil)
+
+       // Download with range, from 1024 to 4096
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), Range(1024, 4095))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err := compareFilesWithRange(fileName, 1024, newFile, 0, 3072)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       err = s.bucket.GetObjectToFile(objectName, newFileGet, Range(1024, 4095))
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download with range, from 1024 to 4096
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 1024, Routines(3), NormalizedRange("1024-4095"))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFilesWithRange(fileName, 1024, newFile, 0, 3072)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("1024-4095"))
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download with range, from 2048 to the end
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 1024*1024, Routines(3), NormalizedRange("2048-"))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFilesWithRange(fileName, 2048, newFile, 0, fileSize-2048)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("2048-"))
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download with range, the last 4096
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 1024, Routines(3), NormalizedRange("-4096"))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFilesWithRange(fileName, fileSize-4096, newFile, 0, 4096)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("-4096"))
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestDownloadWithCheckoutAndRange tests concurrent downloading with range specified and checkpoint enabled
+func (s *OssDownloadSuite) TestDownloadWithCheckoutAndRange(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+       newFileGet := RandStr(8) + "-get.jpg"
+
+       // Upload a file
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, fileName+".cp"))
+       c.Assert(err, IsNil)
+
+       fileSize, err := getFileSize(fileName)
+       c.Assert(err, IsNil)
+
+       // Download with range, from 1024 to 4096
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), Checkpoint(true, newFile+".cp"), Range(1024, 4095))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err := compareFilesWithRange(fileName, 1024, newFile, 0, 3072)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       err = s.bucket.GetObjectToFile(objectName, newFileGet, Range(1024, 4095))
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download with range, from 1024 to 4096
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 1024, Routines(3), Checkpoint(true, newFile+".cp"), NormalizedRange("1024-4095"))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFilesWithRange(fileName, 1024, newFile, 0, 3072)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("1024-4095"))
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download with range, from 2048 to the end
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 1024*1024, Routines(3), Checkpoint(true, newFile+".cp"), NormalizedRange("2048-"))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFilesWithRange(fileName, 2048, newFile, 0, fileSize-2048)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("2048-"))
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download with range, the last 4096 bytes
+       os.Remove(newFile)
+       err = s.bucket.DownloadFile(objectName, newFile, 1024, Routines(3), Checkpoint(true, newFile+".cp"), NormalizedRange("-4096"))
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFilesWithRange(fileName, fileSize-4096, newFile, 0, 4096)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("-4096"))
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestCombineCRCInDownloadParts tests combineCRCInParts
+func (s *OssDownloadSuite) TestCombineCRCInDownloadParts(c *C) {
+       crc := combineCRCInParts(nil)
+       c.Assert(crc == 0, Equals, true)
+
+       crc = combineCRCInParts(make([]downloadPart, 0))
+       c.Assert(crc == 0, Equals, true)
+
+       parts := make([]downloadPart, 1)
+       parts[0].CRC64 = 10278880121275185425
+       crc = combineCRCInParts(parts)
+       c.Assert(crc == 10278880121275185425, Equals, true)
+
+       parts = make([]downloadPart, 2)
+       parts[0].CRC64 = 6748440630437108969
+       parts[0].Start = 0
+       parts[0].End = 4
+       parts[1].CRC64 = 10278880121275185425
+       parts[1].Start = 5
+       parts[1].End = 8
+       crc = combineCRCInParts(parts)
+       c.Assert(crc == 11051210869376104954, Equals, true)
+}
+
+func getFileSize(fileName string) (int64, error) {
+       file, err := os.Open(fileName)
+       if err != nil {
+               return 0, err
+       }
+       defer file.Close()
+
+       stat, err := file.Stat()
+       if err != nil {
+               return 0, err
+       }
+
+       return stat.Size(), nil
+}
+
+// compareFilesWithRange compares the content between fileL and fileR with specified range
+func compareFilesWithRange(fileL string, offsetL int64, fileR string, offsetR int64, size int64) (bool, error) {
+       finL, err := os.Open(fileL)
+       if err != nil {
+               return false, err
+       }
+       defer finL.Close()
+       finL.Seek(offsetL, os.SEEK_SET)
+
+       finR, err := os.Open(fileR)
+       if err != nil {
+               return false, err
+       }
+       defer finR.Close()
+       finR.Seek(offsetR, os.SEEK_SET)
+
+       statL, err := finL.Stat()
+       if err != nil {
+               return false, err
+       }
+
+       statR, err := finR.Stat()
+       if err != nil {
+               return false, err
+       }
+
+       if (offsetL+size > statL.Size()) || (offsetR+size > statR.Size()) {
+               return false, nil
+       }
+
+       part := statL.Size() - offsetL
+       if part > 16*1024 {
+               part = 16 * 1024
+       }
+
+       bufL := make([]byte, part)
+       bufR := make([]byte, part)
+       for readN := int64(0); readN < size; {
+               n, _ := finL.Read(bufL)
+               if 0 == n {
+                       break
+               }
+
+               n, _ = finR.Read(bufR)
+               if 0 == n {
+                       break
+               }
+
+               tailer := part
+               if tailer > size-readN {
+                       tailer = size - readN
+               }
+               readN += tailer
+
+               if !bytes.Equal(bufL[0:tailer], bufR[0:tailer]) {
+                       return false, nil
+               }
+       }
+
+       return true, nil
+}
+
+func (s *OssDownloadSuite) TestVersioningDownloadWithoutCheckPoint(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // begin test
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "test-file-" + RandStr(8)
+       fileData := RandStr(500 * 1024)
+       CreateFile(fileName, fileData, c)
+
+       newFile := RandStr(8) + ".jpg"
+       newFileGet := RandStr(8) + "-.jpg"
+
+       // Upload a file
+       var respHeader http.Header
+       options := []Option{Routines(3), GetResponseHeader(&respHeader)}
+       err = bucket.UploadFile(objectName, fileName, 100*1024, options...)
+       c.Assert(err, IsNil)
+       versionId := GetVersionId(respHeader)
+       c.Assert(len(versionId) > 0, Equals, true)
+
+       fileSize, err := getFileSize(fileName)
+       c.Assert(err, IsNil)
+
+       // overwrite emtpy object
+       err = bucket.PutObject(objectName, strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       // Download with range, from 1024 to 4096
+       os.Remove(newFile)
+       options = []Option{Routines(3), Range(1024, 4095), VersionId(versionId)}
+       err = bucket.DownloadFile(objectName, newFile, 100*1024, options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err := compareFilesWithRange(fileName, 1024, newFile, 0, 3072)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       options = []Option{Range(1024, 4095), VersionId(versionId)}
+       err = bucket.GetObjectToFile(objectName, newFileGet, options...)
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download with range, from 1024 to 4096
+       os.Remove(newFile)
+       options = []Option{Routines(3), NormalizedRange("1024-4095"), VersionId(versionId)}
+       err = bucket.DownloadFile(objectName, newFile, 1024, options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFilesWithRange(fileName, 1024, newFile, 0, 3072)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       options = []Option{NormalizedRange("1024-4095"), VersionId(versionId)}
+       err = bucket.GetObjectToFile(objectName, newFileGet, options...)
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download with range, from 2048 to the end
+       os.Remove(newFile)
+       options = []Option{NormalizedRange("2048-"), VersionId(versionId)}
+       err = bucket.DownloadFile(objectName, newFile, 1024*1024, options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFilesWithRange(fileName, 2048, newFile, 0, fileSize-2048)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       options = []Option{NormalizedRange("2048-"), VersionId(versionId)}
+       err = bucket.GetObjectToFile(objectName, newFileGet, options...)
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download with range, the last 4096
+       os.Remove(newFile)
+       options = []Option{Routines(3), NormalizedRange("-4096"), VersionId(versionId)}
+       err = bucket.DownloadFile(objectName, newFile, 1024, options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       eq, err = compareFilesWithRange(fileName, fileSize-4096, newFile, 0, 4096)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(newFileGet)
+       options = []Option{NormalizedRange("-4096"), VersionId(versionId)}
+       err = bucket.GetObjectToFile(objectName, newFileGet, options...)
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(newFile, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // download whole file
+       os.Remove(newFileGet)
+       options = []Option{Routines(3), VersionId(versionId)}
+       err = bucket.GetObjectToFile(objectName, newFileGet, options...)
+       c.Assert(err, IsNil)
+
+       // Compare get and download
+       eq, err = compareFiles(fileName, newFileGet)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(fileName)
+       os.Remove(newFileGet)
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssDownloadSuite) TestVersioningDownloadWithCheckPoint(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // begin test
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "test-file-" + RandStr(8)
+       fileData := RandStr(500 * 1024)
+       CreateFile(fileName, fileData, c)
+       newFile := RandStr(8) + ".jpg"
+
+       // Upload a file
+       var respHeader http.Header
+       options := []Option{Routines(3), GetResponseHeader(&respHeader)}
+       err = bucket.UploadFile(objectName, fileName, 100*1024, options...)
+       c.Assert(err, IsNil)
+       versionId := GetVersionId(respHeader)
+       c.Assert(len(versionId) > 0, Equals, true)
+
+       // Resumable download with checkpoint dir
+       os.Remove(newFile)
+       downloadPartHooker = DownErrorHooker
+       options = []Option{CheckpointDir(true, "./"), VersionId(versionId)}
+
+       strPayer := getPayer(options)
+       c.Assert(strPayer, Equals, "")
+
+       err = bucket.DownloadFile(objectName, newFile, 100*1024, options...)
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+
+       // download again
+       downloadPartHooker = defaultDownloadPartHook
+       options = []Option{CheckpointDir(true, "./"), VersionId(versionId), GetResponseHeader(&respHeader)}
+       err = bucket.DownloadFile(objectName, newFile, 100*1024, options...)
+       c.Assert(err, IsNil)
+       c.Assert(GetVersionId(respHeader), Equals, versionId)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(fileName)
+       os.Remove(newFile)
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssDownloadSuite) TestdownloadFileChoiceOptions(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // begin test
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "test-file-" + RandStr(8)
+       fileData := RandStr(500 * 1024)
+       CreateFile(fileName, fileData, c)
+       newFile := RandStr(8) + ".jpg"
+
+       // Upload a file
+       var respHeader http.Header
+       options := []Option{Routines(3), GetResponseHeader(&respHeader)}
+       err = bucket.UploadFile(objectName, fileName, 100*1024, options...)
+       c.Assert(err, IsNil)
+
+       // Resumable download with checkpoint dir
+       os.Remove(newFile)
+
+       // downloadFile with properties
+       options = []Option{
+               ObjectACL(ACLPublicRead),
+               RequestPayer(Requester),
+               TrafficLimitHeader(1024 * 1024 * 8),
+       }
+
+       err = bucket.DownloadFile(objectName, newFile, 100*1024, options...)
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(fileName)
+       os.Remove(newFile)
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssDownloadSuite) TestdownloadFileWithCpChoiceOptions(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // begin test
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "test-file-" + RandStr(8)
+       fileData := RandStr(500 * 1024)
+       CreateFile(fileName, fileData, c)
+       newFile := RandStr(8) + ".jpg"
+
+       // Upload a file
+       var respHeader http.Header
+       options := []Option{Routines(3), GetResponseHeader(&respHeader)}
+       err = bucket.UploadFile(objectName, fileName, 100*1024, options...)
+       c.Assert(err, IsNil)
+
+       // Resumable download with checkpoint dir
+       os.Remove(newFile)
+
+       // DownloadFile with properties
+       options = []Option{
+               ObjectACL(ACLPublicRead),
+               RequestPayer(Requester),
+               TrafficLimitHeader(1024 * 1024 * 8),
+               CheckpointDir(true, "./"),
+       }
+
+       err = bucket.DownloadFile(objectName, newFile, 100*1024, options...)
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(fileName)
+       os.Remove(newFile)
+       err = bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+       ForceDeleteBucket(client, bucketName, c)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error.go
new file mode 100644 (file)
index 0000000..a877211
--- /dev/null
@@ -0,0 +1,94 @@
+package oss
+
+import (
+       "encoding/xml"
+       "fmt"
+       "net/http"
+       "strings"
+)
+
+// ServiceError contains fields of the error response from Oss Service REST API.
+type ServiceError struct {
+       XMLName    xml.Name `xml:"Error"`
+       Code       string   `xml:"Code"`      // The error code returned from OSS to the caller
+       Message    string   `xml:"Message"`   // The detail error message from OSS
+       RequestID  string   `xml:"RequestId"` // The UUID used to uniquely identify the request
+       HostID     string   `xml:"HostId"`    // The OSS server cluster's Id
+       Endpoint   string   `xml:"Endpoint"`
+       RawMessage string   // The raw messages from OSS
+       StatusCode int      // HTTP status code
+}
+
+// Error implements interface error
+func (e ServiceError) Error() string {
+       if e.Endpoint == "" {
+               return fmt.Sprintf("oss: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=\"%s\", RequestId=%s",
+                       e.StatusCode, e.Code, e.Message, e.RequestID)
+       }
+       return fmt.Sprintf("oss: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=\"%s\", RequestId=%s, Endpoint=%s",
+               e.StatusCode, e.Code, e.Message, e.RequestID, e.Endpoint)
+}
+
+// UnexpectedStatusCodeError is returned when a storage service responds with neither an error
+// nor with an HTTP status code indicating success.
+type UnexpectedStatusCodeError struct {
+       allowed []int // The expected HTTP stats code returned from OSS
+       got     int   // The actual HTTP status code from OSS
+}
+
+// Error implements interface error
+func (e UnexpectedStatusCodeError) Error() string {
+       s := func(i int) string { return fmt.Sprintf("%d %s", i, http.StatusText(i)) }
+
+       got := s(e.got)
+       expected := []string{}
+       for _, v := range e.allowed {
+               expected = append(expected, s(v))
+       }
+       return fmt.Sprintf("oss: status code from service response is %s; was expecting %s",
+               got, strings.Join(expected, " or "))
+}
+
+// Got is the actual status code returned by oss.
+func (e UnexpectedStatusCodeError) Got() int {
+       return e.got
+}
+
+// CheckRespCode returns UnexpectedStatusError if the given response code is not
+// one of the allowed status codes; otherwise nil.
+func CheckRespCode(respCode int, allowed []int) error {
+       for _, v := range allowed {
+               if respCode == v {
+                       return nil
+               }
+       }
+       return UnexpectedStatusCodeError{allowed, respCode}
+}
+
+// CRCCheckError is returned when crc check is inconsistent between client and server
+type CRCCheckError struct {
+       clientCRC uint64 // Calculated CRC64 in client
+       serverCRC uint64 // Calculated CRC64 in server
+       operation string // Upload operations such as PutObject/AppendObject/UploadPart, etc
+       requestID string // The request id of this operation
+}
+
+// Error implements interface error
+func (e CRCCheckError) Error() string {
+       return fmt.Sprintf("oss: the crc of %s is inconsistent, client %d but server %d; request id is %s",
+               e.operation, e.clientCRC, e.serverCRC, e.requestID)
+}
+
+func CheckDownloadCRC(clientCRC, serverCRC uint64) error {
+       if clientCRC == serverCRC {
+               return nil
+       }
+       return CRCCheckError{clientCRC, serverCRC, "DownloadFile", ""}
+}
+
+func CheckCRC(resp *Response, operation string) error {
+       if resp.Headers.Get(HTTPHeaderOssCRC64) == "" || resp.ClientCRC == resp.ServerCRC {
+               return nil
+       }
+       return CRCCheckError{resp.ClientCRC, resp.ServerCRC, operation, resp.Headers.Get(HTTPHeaderOssRequestID)}
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error_test.go
new file mode 100644 (file)
index 0000000..49280cd
--- /dev/null
@@ -0,0 +1,111 @@
+package oss
+
+import (
+       "math"
+       "net/http"
+       "strings"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssErrorSuite struct{}
+
+var _ = Suite(&OssErrorSuite{})
+
+func (s *OssErrorSuite) TestCheckCRCHasCRCInResp(c *C) {
+       headers := http.Header{
+               "Expires":              {"-1"},
+               "Content-Length":       {"0"},
+               "Content-Encoding":     {"gzip"},
+               "X-Oss-Hash-Crc64ecma": {"0"},
+       }
+
+       resp := &Response{
+               StatusCode: 200,
+               Headers:    headers,
+               Body:       nil,
+               ClientCRC:  math.MaxUint64,
+               ServerCRC:  math.MaxUint64,
+       }
+
+       err := CheckCRC(resp, "test")
+       c.Assert(err, IsNil)
+}
+
+func (s *OssErrorSuite) TestCheckCRCNotHasCRCInResp(c *C) {
+       headers := http.Header{
+               "Expires":          {"-1"},
+               "Content-Length":   {"0"},
+               "Content-Encoding": {"gzip"},
+       }
+
+       resp := &Response{
+               StatusCode: 200,
+               Headers:    headers,
+               Body:       nil,
+               ClientCRC:  math.MaxUint64,
+               ServerCRC:  math.MaxUint32,
+       }
+
+       err := CheckCRC(resp, "test")
+       c.Assert(err, IsNil)
+}
+
+func (s *OssErrorSuite) TestCheckCRCCNegative(c *C) {
+       headers := http.Header{
+               "Expires":              {"-1"},
+               "Content-Length":       {"0"},
+               "Content-Encoding":     {"gzip"},
+               "X-Oss-Hash-Crc64ecma": {"0"},
+       }
+
+       resp := &Response{
+               StatusCode: 200,
+               Headers:    headers,
+               Body:       nil,
+               ClientCRC:  0,
+               ServerCRC:  math.MaxUint64,
+       }
+
+       err := CheckCRC(resp, "test")
+       c.Assert(err, NotNil)
+       testLogger.Println("error:", err)
+}
+
+func (s *OssErrorSuite) TestCheckDownloadCRC(c *C) {
+       err := CheckDownloadCRC(0xFBF9D9603A6FA020, 0xFBF9D9603A6FA020)
+       c.Assert(err, IsNil)
+
+       err = CheckDownloadCRC(0, 0)
+       c.Assert(err, IsNil)
+
+       err = CheckDownloadCRC(0xDB6EFFF26AA94946, 0)
+       c.Assert(err, NotNil)
+       testLogger.Println("error:", err)
+}
+
+func (s *OssErrorSuite) TestServiceErrorEndPoint(c *C) {
+       xmlBody := `<?xml version="1.0" encoding="UTF-8"?>
+       <Error>
+         <Code>AccessDenied</Code>
+         <Message>The bucket you visit is not belong to you.</Message>
+         <RequestId>5C1B5E9BD79A6B9B6466166E</RequestId>
+         <HostId>oss-c-sdk-test-verify-b.oss-cn-shenzhen.aliyuncs.com</HostId>
+       </Error>`
+       serverError, _ := serviceErrFromXML([]byte(xmlBody), 403, "5C1B5E9BD79A6B9B6466166E")
+       errMsg := serverError.Error()
+       c.Assert(strings.Contains(errMsg, "Endpoint="), Equals, false)
+
+       xmlBodyWithEndPoint := `<?xml version="1.0" encoding="UTF-8"?>
+       <Error>
+      <Code>AccessDenied</Code>
+         <Message>The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.</Message>
+         <RequestId>5C1B595ED51820B569C6A12F</RequestId>
+         <HostId>hello-hangzws.oss-cn-qingdao.aliyuncs.com</HostId>
+         <Bucket>hello-hangzws</Bucket>
+         <Endpoint>oss-cn-shenzhen.aliyuncs.com</Endpoint>
+       </Error>`
+       serverError, _ = serviceErrFromXML([]byte(xmlBodyWithEndPoint), 406, "5C1B595ED51820B569C6A12F")
+       errMsg = serverError.Error()
+       c.Assert(strings.Contains(errMsg, "Endpoint=oss-cn-shenzhen.aliyuncs.com"), Equals, true)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_6.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_6.go
new file mode 100644 (file)
index 0000000..943dc8f
--- /dev/null
@@ -0,0 +1,28 @@
+// +build !go1.7
+
+// "golang.org/x/time/rate" is depended on golang context package  go1.7 onward
+// this file is only for build,not supports limit upload speed
+package oss
+
+import (
+       "fmt"
+       "io"
+)
+
+const (
+       perTokenBandwidthSize int = 1024
+)
+
+type OssLimiter struct {
+}
+
+type LimitSpeedReader struct {
+       io.ReadCloser
+       reader     io.Reader
+       ossLimiter *OssLimiter
+}
+
+func GetOssLimiter(uploadSpeed int) (ossLimiter *OssLimiter, err error) {
+       err = fmt.Errorf("rate.Limiter is not supported below version go1.7")
+       return nil, err
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_7.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_7.go
new file mode 100644 (file)
index 0000000..f6baf29
--- /dev/null
@@ -0,0 +1,90 @@
+// +build go1.7
+
+package oss
+
+import (
+       "fmt"
+       "io"
+       "math"
+       "time"
+
+       "golang.org/x/time/rate"
+)
+
+const (
+       perTokenBandwidthSize int = 1024
+)
+
+// OssLimiter wrapper rate.Limiter
+type OssLimiter struct {
+       limiter *rate.Limiter
+}
+
+// GetOssLimiter create OssLimiter
+// uploadSpeed KB/s
+func GetOssLimiter(uploadSpeed int) (ossLimiter *OssLimiter, err error) {
+       limiter := rate.NewLimiter(rate.Limit(uploadSpeed), uploadSpeed)
+
+       // first consume the initial full token,the limiter will behave more accurately
+       limiter.AllowN(time.Now(), uploadSpeed)
+
+       return &OssLimiter{
+               limiter: limiter,
+       }, nil
+}
+
+// LimitSpeedReader for limit bandwidth upload
+type LimitSpeedReader struct {
+       io.ReadCloser
+       reader     io.Reader
+       ossLimiter *OssLimiter
+}
+
+// Read
+func (r *LimitSpeedReader) Read(p []byte) (n int, err error) {
+       n = 0
+       err = nil
+       start := 0
+       burst := r.ossLimiter.limiter.Burst()
+       var end int
+       var tmpN int
+       var tc int
+       for start < len(p) {
+               if start+burst*perTokenBandwidthSize < len(p) {
+                       end = start + burst*perTokenBandwidthSize
+               } else {
+                       end = len(p)
+               }
+
+               tmpN, err = r.reader.Read(p[start:end])
+               if tmpN > 0 {
+                       n += tmpN
+                       start = n
+               }
+
+               if err != nil {
+                       return
+               }
+
+               tc = int(math.Ceil(float64(tmpN) / float64(perTokenBandwidthSize)))
+               now := time.Now()
+               re := r.ossLimiter.limiter.ReserveN(now, tc)
+               if !re.OK() {
+                       err = fmt.Errorf("LimitSpeedReader.Read() failure,ReserveN error,start:%d,end:%d,burst:%d,perTokenBandwidthSize:%d",
+                               start, end, burst, perTokenBandwidthSize)
+                       return
+               }
+               timeDelay := re.Delay()
+               time.Sleep(timeDelay)
+       }
+       return
+}
+
+// Close ...
+func (r *LimitSpeedReader) Close() error {
+       rc, ok := r.reader.(io.ReadCloser)
+       if ok {
+               return rc.Close()
+       }
+       return nil
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel.go
new file mode 100644 (file)
index 0000000..bf5ba07
--- /dev/null
@@ -0,0 +1,257 @@
+package oss
+
+import (
+       "bytes"
+       "encoding/xml"
+       "fmt"
+       "io"
+       "net/http"
+       "strconv"
+       "time"
+)
+
+//
+// CreateLiveChannel    create a live-channel
+//
+// channelName  the name of the channel
+// config       configuration of the channel
+//
+// CreateLiveChannelResult  the result of create live-channel
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) CreateLiveChannel(channelName string, config LiveChannelConfiguration) (CreateLiveChannelResult, error) {
+       var out CreateLiveChannelResult
+
+       bs, err := xml.Marshal(config)
+       if err != nil {
+               return out, err
+       }
+
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       params := map[string]interface{}{}
+       params["live"] = nil
+       resp, err := bucket.do("PUT", channelName, params, nil, buffer, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+//
+// PutLiveChannelStatus Set the status of the live-channel: enabled/disabled
+//
+// channelName  the name of the channel
+// status       enabled/disabled
+//
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) PutLiveChannelStatus(channelName, status string) error {
+       params := map[string]interface{}{}
+       params["live"] = nil
+       params["status"] = status
+
+       resp, err := bucket.do("PUT", channelName, params, nil, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// PostVodPlaylist  create an playlist based on the specified playlist name, startTime and endTime
+//
+// channelName  the name of the channel
+// playlistName the name of the playlist, must end with ".m3u8"
+// startTime    the start time of the playlist
+// endTime      the endtime of the playlist
+//
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) PostVodPlaylist(channelName, playlistName string, startTime, endTime time.Time) error {
+       params := map[string]interface{}{}
+       params["vod"] = nil
+       params["startTime"] = strconv.FormatInt(startTime.Unix(), 10)
+       params["endTime"] = strconv.FormatInt(endTime.Unix(), 10)
+
+       key := fmt.Sprintf("%s/%s", channelName, playlistName)
+       resp, err := bucket.do("POST", key, params, nil, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetVodPlaylist  get the playlist based on the specified channelName, startTime and endTime
+//
+// channelName  the name of the channel
+// startTime    the start time of the playlist
+// endTime      the endtime of the playlist
+//
+// io.ReadCloser reader instance for reading data from response. It must be called close() after the usage and only valid when error is nil.
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) GetVodPlaylist(channelName string, startTime, endTime time.Time) (io.ReadCloser, error) {
+       params := map[string]interface{}{}
+       params["vod"] = nil
+       params["startTime"] = strconv.FormatInt(startTime.Unix(), 10)
+       params["endTime"] = strconv.FormatInt(endTime.Unix(), 10)
+
+       resp, err := bucket.do("GET", channelName, params, nil, nil, nil)
+       if err != nil {
+               return nil, err
+       }
+
+       return resp.Body, nil
+}
+
+//
+// GetLiveChannelStat   Get the state of the live-channel
+//
+// channelName  the name of the channel
+//
+// LiveChannelStat  the state of the live-channel
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) GetLiveChannelStat(channelName string) (LiveChannelStat, error) {
+       var out LiveChannelStat
+       params := map[string]interface{}{}
+       params["live"] = nil
+       params["comp"] = "stat"
+
+       resp, err := bucket.do("GET", channelName, params, nil, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+//
+// GetLiveChannelInfo   Get the configuration info of the live-channel
+//
+// channelName  the name of the channel
+//
+// LiveChannelConfiguration the configuration info of the live-channel
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) GetLiveChannelInfo(channelName string) (LiveChannelConfiguration, error) {
+       var out LiveChannelConfiguration
+       params := map[string]interface{}{}
+       params["live"] = nil
+
+       resp, err := bucket.do("GET", channelName, params, nil, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+//
+// GetLiveChannelHistory    Get push records of live-channel
+//
+// channelName  the name of the channel
+//
+// LiveChannelHistory   push records
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) GetLiveChannelHistory(channelName string) (LiveChannelHistory, error) {
+       var out LiveChannelHistory
+       params := map[string]interface{}{}
+       params["live"] = nil
+       params["comp"] = "history"
+
+       resp, err := bucket.do("GET", channelName, params, nil, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+//
+// ListLiveChannel  list the live-channels
+//
+// options  Prefix: filter by the name start with the value of "Prefix"
+//          MaxKeys: the maximum count returned
+//          Marker: cursor from which starting list
+//
+// ListLiveChannelResult    live-channel list
+// error    nil if success, otherwise error
+//
+func (bucket Bucket) ListLiveChannel(options ...Option) (ListLiveChannelResult, error) {
+       var out ListLiveChannelResult
+
+       params, err := GetRawParams(options)
+       if err != nil {
+               return out, err
+       }
+
+       params["live"] = nil
+
+       resp, err := bucket.do("GET", "", params, nil, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+//
+// DeleteLiveChannel    Delete the live-channel. When a client trying to stream the live-channel, the operation will fail. it will only delete the live-channel itself and the object generated by the live-channel will not be deleted.
+//
+// channelName  the name of the channel
+//
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) DeleteLiveChannel(channelName string) error {
+       params := map[string]interface{}{}
+       params["live"] = nil
+
+       if channelName == "" {
+               return fmt.Errorf("invalid argument: channel name is empty")
+       }
+
+       resp, err := bucket.do("DELETE", channelName, params, nil, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+//
+// SignRtmpURL  Generate a RTMP push-stream signature URL for the trusted user to push the RTMP stream to the live-channel.
+//
+// channelName  the name of the channel
+// playlistName the name of the playlist, must end with ".m3u8"
+// expires      expiration (in seconds)
+//
+// string       singed rtmp push stream url
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) SignRtmpURL(channelName, playlistName string, expires int64) (string, error) {
+       if expires <= 0 {
+               return "", fmt.Errorf("invalid argument: %d, expires must greater than 0", expires)
+       }
+       expiration := time.Now().Unix() + expires
+
+       return bucket.Client.Conn.signRtmpURL(bucket.BucketName, channelName, playlistName, expiration), nil
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel_test.go
new file mode 100644 (file)
index 0000000..26ed5ec
--- /dev/null
@@ -0,0 +1,428 @@
+package oss
+
+import (
+       "fmt"
+       "strings"
+       "time"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssBucketLiveChannelSuite struct {
+       client *Client
+       bucket *Bucket
+}
+
+var _ = Suite(&OssBucketLiveChannelSuite{})
+
+// SetUpSuite Run once when the suite starts running
+func (s *OssBucketLiveChannelSuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       s.client = client
+
+       err = s.client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       time.Sleep(5 * time.Second)
+
+       bucket, err := s.client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       s.bucket = bucket
+
+       testLogger.Println("test livechannel started...")
+}
+
+// TearDownSuite Run once after all tests or benchmarks
+func (s *OssBucketLiveChannelSuite) TearDownSuite(c *C) {
+       marker := ""
+       for {
+               result, err := s.bucket.ListLiveChannel(Marker(marker))
+               c.Assert(err, IsNil)
+
+               for _, channel := range result.LiveChannel {
+                       err := s.bucket.DeleteLiveChannel(channel.Name)
+                       c.Assert(err, IsNil)
+               }
+
+               if result.IsTruncated {
+                       marker = result.NextMarker
+               } else {
+                       break
+               }
+       }
+
+       testLogger.Println("test livechannel done...")
+}
+
+// SetUpTest Run before each test or benchmark starts
+func (s *OssBucketLiveChannelSuite) SetUpTest(c *C) {
+
+}
+
+// TearDownTest        Run after each test or benchmark runs.
+func (s *OssBucketLiveChannelSuite) TearDownTest(c *C) {
+
+}
+
+// TestCreateLiveChannel
+func (s *OssBucketLiveChannelSuite) TestCreateLiveChannel(c *C) {
+       channelName := "test-create-channel"
+       playlistName := "test-create-channel.m3u8"
+
+       target := LiveChannelTarget{
+               PlaylistName: playlistName,
+               Type:         "HLS",
+       }
+
+       config := LiveChannelConfiguration{
+               Description: "livechannel for test",
+               Status:      "enabled",
+               Target:      target,
+       }
+
+       result, err := s.bucket.CreateLiveChannel(channelName, config)
+       c.Assert(err, IsNil)
+
+       playURL := getPlayURL(bucketName, channelName, playlistName)
+       publishURL := getPublishURL(bucketName, channelName)
+       c.Assert(result.PlayUrls[0], Equals, playURL)
+       c.Assert(result.PublishUrls[0], Equals, publishURL)
+
+       err = s.bucket.DeleteLiveChannel(channelName)
+       c.Assert(err, IsNil)
+
+       invalidType := RandStr(4)
+       invalidTarget := LiveChannelTarget{
+               PlaylistName: playlistName,
+               Type:         invalidType,
+       }
+
+       invalidConfig := LiveChannelConfiguration{
+               Description: "livechannel for test",
+               Status:      "enabled",
+               Target:      invalidTarget,
+       }
+
+       _, err = s.bucket.CreateLiveChannel(channelName, invalidConfig)
+       c.Assert(err, NotNil)
+}
+
+// TestDeleteLiveChannel
+func (s *OssBucketLiveChannelSuite) TestDeleteLiveChannel(c *C) {
+       channelName := "test-delete-channel"
+
+       target := LiveChannelTarget{
+               Type: "HLS",
+       }
+
+       config := LiveChannelConfiguration{
+               Target: target,
+       }
+
+       _, err := s.bucket.CreateLiveChannel(channelName, config)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteLiveChannel(channelName)
+       c.Assert(err, IsNil)
+
+       emptyChannelName := ""
+       err = s.bucket.DeleteLiveChannel(emptyChannelName)
+       c.Assert(err, NotNil)
+
+       longChannelName := RandStr(65)
+       err = s.bucket.DeleteLiveChannel(longChannelName)
+       c.Assert(err, NotNil)
+
+       config, err = s.bucket.GetLiveChannelInfo(channelName)
+       c.Assert(err, NotNil)
+}
+
+// TestGetLiveChannelInfo
+func (s *OssBucketLiveChannelSuite) TestGetLiveChannelInfo(c *C) {
+       channelName := "test-get-channel-status"
+
+       _, err := s.bucket.GetLiveChannelInfo(channelName)
+       c.Assert(err, NotNil)
+
+       createCfg := LiveChannelConfiguration{
+               Target: LiveChannelTarget{
+                       Type:         "HLS",
+                       FragDuration: 10,
+                       FragCount:    4,
+                       PlaylistName: "test-get-channel-status.m3u8",
+               },
+       }
+
+       _, err = s.bucket.CreateLiveChannel(channelName, createCfg)
+       c.Assert(err, IsNil)
+
+       getCfg, err := s.bucket.GetLiveChannelInfo(channelName)
+       c.Assert(err, IsNil)
+       c.Assert("enabled", Equals, getCfg.Status) //The default value is enabled
+       c.Assert("HLS", Equals, getCfg.Target.Type)
+       c.Assert(10, Equals, getCfg.Target.FragDuration)
+       c.Assert(4, Equals, getCfg.Target.FragCount)
+       c.Assert("test-get-channel-status.m3u8", Equals, getCfg.Target.PlaylistName)
+
+       err = s.bucket.DeleteLiveChannel(channelName)
+       c.Assert(err, IsNil)
+}
+
+// TestPutLiveChannelStatus
+func (s *OssBucketLiveChannelSuite) TestPutLiveChannelStatus(c *C) {
+       channelName := "test-put-channel-status"
+
+       config := LiveChannelConfiguration{
+               Status: "disabled",
+               Target: LiveChannelTarget{
+                       Type: "HLS",
+               },
+       }
+
+       _, err := s.bucket.CreateLiveChannel(channelName, config)
+       c.Assert(err, IsNil)
+       getCfg, err := s.bucket.GetLiveChannelInfo(channelName)
+       c.Assert(err, IsNil)
+       c.Assert("disabled", Equals, getCfg.Status)
+
+       err = s.bucket.PutLiveChannelStatus(channelName, "enabled")
+       c.Assert(err, IsNil)
+       getCfg, err = s.bucket.GetLiveChannelInfo(channelName)
+       c.Assert(err, IsNil)
+       c.Assert("enabled", Equals, getCfg.Status)
+
+       err = s.bucket.PutLiveChannelStatus(channelName, "disabled")
+       c.Assert(err, IsNil)
+       getCfg, err = s.bucket.GetLiveChannelInfo(channelName)
+       c.Assert(err, IsNil)
+       c.Assert("disabled", Equals, getCfg.Status)
+
+       invalidStatus := RandLowStr(9)
+       err = s.bucket.PutLiveChannelStatus(channelName, invalidStatus)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DeleteLiveChannel(channelName)
+       c.Assert(err, IsNil)
+}
+
+// TestGetLiveChannelHistory
+func (s *OssBucketLiveChannelSuite) TestGetLiveChannelHistory(c *C) {
+       channelName := "test-get-channel-history"
+
+       _, err := s.bucket.GetLiveChannelHistory(channelName)
+       c.Assert(err, NotNil)
+
+       config := LiveChannelConfiguration{
+               Target: LiveChannelTarget{
+                       Type: "HLS",
+               },
+       }
+
+       _, err = s.bucket.CreateLiveChannel(channelName, config)
+       c.Assert(err, IsNil)
+
+       history, err := s.bucket.GetLiveChannelHistory(channelName)
+       c.Assert(err, IsNil)
+       c.Assert(len(history.Record), Equals, 0)
+
+       err = s.bucket.DeleteLiveChannel(channelName)
+       c.Assert(err, IsNil)
+}
+
+// TestGetLiveChannelStat
+func (s *OssBucketLiveChannelSuite) TestGetLiveChannelStat(c *C) {
+       channelName := "test-get-channel-stat"
+
+       _, err := s.bucket.GetLiveChannelStat(channelName)
+       c.Assert(err, NotNil)
+
+       config := LiveChannelConfiguration{
+               Target: LiveChannelTarget{
+                       Type: "HLS",
+               },
+       }
+
+       _, err = s.bucket.CreateLiveChannel(channelName, config)
+       c.Assert(err, IsNil)
+
+       stat, err := s.bucket.GetLiveChannelStat(channelName)
+       c.Assert(err, IsNil)
+       c.Assert(stat.Status, Equals, "Idle")
+
+       err = s.bucket.DeleteLiveChannel(channelName)
+       c.Assert(err, IsNil)
+}
+
+// TestPostVodPlaylist
+func (s *OssBucketLiveChannelSuite) TestPostVodPlaylist(c *C) {
+       channelName := "test-post-vod-playlist"
+       playlistName := "test-post-vod-playlist.m3u8"
+
+       config := LiveChannelConfiguration{
+               Target: LiveChannelTarget{
+                       Type: "HLS",
+               },
+       }
+
+       _, err := s.bucket.CreateLiveChannel(channelName, config)
+       c.Assert(err, IsNil)
+
+       endTime := time.Now().Add(-1 * time.Minute)
+       startTime := endTime.Add(-60 * time.Minute)
+
+       err = s.bucket.PostVodPlaylist(channelName, playlistName, startTime, endTime)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DeleteLiveChannel(channelName)
+       c.Assert(err, IsNil)
+}
+
+// TestPostVodPlaylist
+func (s *OssBucketLiveChannelSuite) TestGetVodPlaylist(c *C) {
+       channelName := "test-get-vod-playlist"
+
+       config := LiveChannelConfiguration{
+               Target: LiveChannelTarget{
+                       Type: "HLS",
+               },
+       }
+
+       _, err := s.bucket.CreateLiveChannel(channelName, config)
+       c.Assert(err, IsNil)
+
+       endTime := time.Now().Add(-1 * time.Minute)
+       startTime := endTime.Add(-60 * time.Minute)
+
+       _, err = s.bucket.GetVodPlaylist(channelName, startTime, endTime)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DeleteLiveChannel(channelName)
+       c.Assert(err, IsNil)
+}
+
+// TestListLiveChannel
+func (s *OssBucketLiveChannelSuite) TestListLiveChannel(c *C) {
+       result, err := s.bucket.ListLiveChannel()
+       c.Assert(err, IsNil)
+       ok := compareListResult(result, "", "", "", 100, false, 0)
+       c.Assert(ok, Equals, true)
+
+       prefix := "test-list-channel"
+       for i := 0; i < 200; i++ {
+               channelName := fmt.Sprintf("%s-%03d", prefix, i)
+
+               config := LiveChannelConfiguration{
+                       Target: LiveChannelTarget{
+                               Type: "HLS",
+                       },
+               }
+
+               _, err := s.bucket.CreateLiveChannel(channelName, config)
+               c.Assert(err, IsNil)
+       }
+
+       result, err = s.bucket.ListLiveChannel()
+       c.Assert(err, IsNil)
+       nextMarker := fmt.Sprintf("%s-099", prefix)
+       ok = compareListResult(result, "", "", nextMarker, 100, true, 100)
+       c.Assert(ok, Equals, true)
+
+       randPrefix := RandStr(5)
+       result, err = s.bucket.ListLiveChannel(Prefix(randPrefix))
+       c.Assert(err, IsNil)
+       ok = compareListResult(result, randPrefix, "", "", 100, false, 0)
+       c.Assert(ok, Equals, true)
+
+       marker := fmt.Sprintf("%s-100", prefix)
+       result, err = s.bucket.ListLiveChannel(Prefix(prefix), Marker(marker))
+       c.Assert(err, IsNil)
+       ok = compareListResult(result, prefix, marker, "", 100, false, 99)
+       c.Assert(ok, Equals, true)
+
+       maxKeys := 1000
+       result, err = s.bucket.ListLiveChannel(MaxKeys(maxKeys))
+       c.Assert(err, IsNil)
+       ok = compareListResult(result, "", "", "", maxKeys, false, 200)
+
+       invalidMaxKeys := -1
+       result, err = s.bucket.ListLiveChannel(MaxKeys(invalidMaxKeys))
+       c.Assert(err, NotNil)
+
+       for i := 0; i < 200; i++ {
+               channelName := fmt.Sprintf("%s-%03d", prefix, i)
+               err := s.bucket.DeleteLiveChannel(channelName)
+               c.Assert(err, IsNil)
+       }
+}
+
+// TestSignRtmpURL
+func (s *OssBucketLiveChannelSuite) TestSignRtmpURL(c *C) {
+       channelName := "test-sign-rtmp-url"
+       playlistName := "test-sign-rtmp-url.m3u8"
+
+       config := LiveChannelConfiguration{
+               Target: LiveChannelTarget{
+                       Type:         "HLS",
+                       PlaylistName: playlistName,
+               },
+       }
+
+       _, err := s.bucket.CreateLiveChannel(channelName, config)
+       c.Assert(err, IsNil)
+
+       expires := int64(3600)
+       signedRtmpURL, err := s.bucket.SignRtmpURL(channelName, playlistName, expires)
+       c.Assert(err, IsNil)
+       playURL := getPublishURL(s.bucket.BucketName, channelName)
+       hasPrefix := strings.HasPrefix(signedRtmpURL, playURL)
+       c.Assert(hasPrefix, Equals, true)
+
+       invalidExpires := int64(-1)
+       signedRtmpURL, err = s.bucket.SignRtmpURL(channelName, playlistName, invalidExpires)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.DeleteLiveChannel(channelName)
+       c.Assert(err, IsNil)
+}
+
+// private
+// getPlayURL Get the play url of the live channel
+func getPlayURL(bucketName, channelName, playlistName string) string {
+       host := ""
+       useHTTPS := false
+       if strings.Contains(endpoint, "https://") {
+               host = endpoint[8:]
+               useHTTPS = true
+       } else if strings.Contains(endpoint, "http://") {
+               host = endpoint[7:]
+       } else {
+               host = endpoint
+       }
+
+       if useHTTPS {
+               return fmt.Sprintf("https://%s.%s/%s/%s", bucketName, host, channelName, playlistName)
+       }
+       return fmt.Sprintf("http://%s.%s/%s/%s", bucketName, host, channelName, playlistName)
+}
+
+// getPublistURL Get the push url of the live stream channel
+func getPublishURL(bucketName, channelName string) string {
+       host := ""
+       if strings.Contains(endpoint, "https://") {
+               host = endpoint[8:]
+       } else if strings.Contains(endpoint, "http://") {
+               host = endpoint[7:]
+       } else {
+               host = endpoint
+       }
+
+       return fmt.Sprintf("rtmp://%s.%s/live/%s", bucketName, host, channelName)
+}
+
+func compareListResult(result ListLiveChannelResult, prefix, marker, nextMarker string, maxKey int, isTruncated bool, count int) bool {
+       if result.Prefix != prefix || result.Marker != marker || result.NextMarker != nextMarker || result.MaxKeys != maxKey || result.IsTruncated != isTruncated || len(result.LiveChannel) != count {
+               return false
+       }
+
+       return true
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/mime.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/mime.go
new file mode 100644 (file)
index 0000000..64f4dcc
--- /dev/null
@@ -0,0 +1,572 @@
+package oss
+
+import (
+       "mime"
+       "path"
+       "strings"
+)
+
+var extToMimeType = map[string]string{
+       ".xlsx":          "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+       ".xltx":          "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
+       ".potx":          "application/vnd.openxmlformats-officedocument.presentationml.template",
+       ".ppsx":          "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
+       ".pptx":          "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+       ".sldx":          "application/vnd.openxmlformats-officedocument.presentationml.slide",
+       ".docx":          "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+       ".dotx":          "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
+       ".xlam":          "application/vnd.ms-excel.addin.macroEnabled.12",
+       ".xlsb":          "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
+       ".apk":           "application/vnd.android.package-archive",
+       ".hqx":           "application/mac-binhex40",
+       ".cpt":           "application/mac-compactpro",
+       ".doc":           "application/msword",
+       ".ogg":           "application/ogg",
+       ".pdf":           "application/pdf",
+       ".rtf":           "text/rtf",
+       ".mif":           "application/vnd.mif",
+       ".xls":           "application/vnd.ms-excel",
+       ".ppt":           "application/vnd.ms-powerpoint",
+       ".odc":           "application/vnd.oasis.opendocument.chart",
+       ".odb":           "application/vnd.oasis.opendocument.database",
+       ".odf":           "application/vnd.oasis.opendocument.formula",
+       ".odg":           "application/vnd.oasis.opendocument.graphics",
+       ".otg":           "application/vnd.oasis.opendocument.graphics-template",
+       ".odi":           "application/vnd.oasis.opendocument.image",
+       ".odp":           "application/vnd.oasis.opendocument.presentation",
+       ".otp":           "application/vnd.oasis.opendocument.presentation-template",
+       ".ods":           "application/vnd.oasis.opendocument.spreadsheet",
+       ".ots":           "application/vnd.oasis.opendocument.spreadsheet-template",
+       ".odt":           "application/vnd.oasis.opendocument.text",
+       ".odm":           "application/vnd.oasis.opendocument.text-master",
+       ".ott":           "application/vnd.oasis.opendocument.text-template",
+       ".oth":           "application/vnd.oasis.opendocument.text-web",
+       ".sxw":           "application/vnd.sun.xml.writer",
+       ".stw":           "application/vnd.sun.xml.writer.template",
+       ".sxc":           "application/vnd.sun.xml.calc",
+       ".stc":           "application/vnd.sun.xml.calc.template",
+       ".sxd":           "application/vnd.sun.xml.draw",
+       ".std":           "application/vnd.sun.xml.draw.template",
+       ".sxi":           "application/vnd.sun.xml.impress",
+       ".sti":           "application/vnd.sun.xml.impress.template",
+       ".sxg":           "application/vnd.sun.xml.writer.global",
+       ".sxm":           "application/vnd.sun.xml.math",
+       ".sis":           "application/vnd.symbian.install",
+       ".wbxml":         "application/vnd.wap.wbxml",
+       ".wmlc":          "application/vnd.wap.wmlc",
+       ".wmlsc":         "application/vnd.wap.wmlscriptc",
+       ".bcpio":         "application/x-bcpio",
+       ".torrent":       "application/x-bittorrent",
+       ".bz2":           "application/x-bzip2",
+       ".vcd":           "application/x-cdlink",
+       ".pgn":           "application/x-chess-pgn",
+       ".cpio":          "application/x-cpio",
+       ".csh":           "application/x-csh",
+       ".dvi":           "application/x-dvi",
+       ".spl":           "application/x-futuresplash",
+       ".gtar":          "application/x-gtar",
+       ".hdf":           "application/x-hdf",
+       ".jar":           "application/x-java-archive",
+       ".jnlp":          "application/x-java-jnlp-file",
+       ".js":            "application/x-javascript",
+       ".ksp":           "application/x-kspread",
+       ".chrt":          "application/x-kchart",
+       ".kil":           "application/x-killustrator",
+       ".latex":         "application/x-latex",
+       ".rpm":           "application/x-rpm",
+       ".sh":            "application/x-sh",
+       ".shar":          "application/x-shar",
+       ".swf":           "application/x-shockwave-flash",
+       ".sit":           "application/x-stuffit",
+       ".sv4cpio":       "application/x-sv4cpio",
+       ".sv4crc":        "application/x-sv4crc",
+       ".tar":           "application/x-tar",
+       ".tcl":           "application/x-tcl",
+       ".tex":           "application/x-tex",
+       ".man":           "application/x-troff-man",
+       ".me":            "application/x-troff-me",
+       ".ms":            "application/x-troff-ms",
+       ".ustar":         "application/x-ustar",
+       ".src":           "application/x-wais-source",
+       ".zip":           "application/zip",
+       ".m3u":           "audio/x-mpegurl",
+       ".ra":            "audio/x-pn-realaudio",
+       ".wav":           "audio/x-wav",
+       ".wma":           "audio/x-ms-wma",
+       ".wax":           "audio/x-ms-wax",
+       ".pdb":           "chemical/x-pdb",
+       ".xyz":           "chemical/x-xyz",
+       ".bmp":           "image/bmp",
+       ".gif":           "image/gif",
+       ".ief":           "image/ief",
+       ".png":           "image/png",
+       ".wbmp":          "image/vnd.wap.wbmp",
+       ".ras":           "image/x-cmu-raster",
+       ".pnm":           "image/x-portable-anymap",
+       ".pbm":           "image/x-portable-bitmap",
+       ".pgm":           "image/x-portable-graymap",
+       ".ppm":           "image/x-portable-pixmap",
+       ".rgb":           "image/x-rgb",
+       ".xbm":           "image/x-xbitmap",
+       ".xpm":           "image/x-xpixmap",
+       ".xwd":           "image/x-xwindowdump",
+       ".css":           "text/css",
+       ".rtx":           "text/richtext",
+       ".tsv":           "text/tab-separated-values",
+       ".jad":           "text/vnd.sun.j2me.app-descriptor",
+       ".wml":           "text/vnd.wap.wml",
+       ".wmls":          "text/vnd.wap.wmlscript",
+       ".etx":           "text/x-setext",
+       ".mxu":           "video/vnd.mpegurl",
+       ".flv":           "video/x-flv",
+       ".wm":            "video/x-ms-wm",
+       ".wmv":           "video/x-ms-wmv",
+       ".wmx":           "video/x-ms-wmx",
+       ".wvx":           "video/x-ms-wvx",
+       ".avi":           "video/x-msvideo",
+       ".movie":         "video/x-sgi-movie",
+       ".ice":           "x-conference/x-cooltalk",
+       ".3gp":           "video/3gpp",
+       ".ai":            "application/postscript",
+       ".aif":           "audio/x-aiff",
+       ".aifc":          "audio/x-aiff",
+       ".aiff":          "audio/x-aiff",
+       ".asc":           "text/plain",
+       ".atom":          "application/atom+xml",
+       ".au":            "audio/basic",
+       ".bin":           "application/octet-stream",
+       ".cdf":           "application/x-netcdf",
+       ".cgm":           "image/cgm",
+       ".class":         "application/octet-stream",
+       ".dcr":           "application/x-director",
+       ".dif":           "video/x-dv",
+       ".dir":           "application/x-director",
+       ".djv":           "image/vnd.djvu",
+       ".djvu":          "image/vnd.djvu",
+       ".dll":           "application/octet-stream",
+       ".dmg":           "application/octet-stream",
+       ".dms":           "application/octet-stream",
+       ".dtd":           "application/xml-dtd",
+       ".dv":            "video/x-dv",
+       ".dxr":           "application/x-director",
+       ".eps":           "application/postscript",
+       ".exe":           "application/octet-stream",
+       ".ez":            "application/andrew-inset",
+       ".gram":          "application/srgs",
+       ".grxml":         "application/srgs+xml",
+       ".gz":            "application/x-gzip",
+       ".htm":           "text/html",
+       ".html":          "text/html",
+       ".ico":           "image/x-icon",
+       ".ics":           "text/calendar",
+       ".ifb":           "text/calendar",
+       ".iges":          "model/iges",
+       ".igs":           "model/iges",
+       ".jp2":           "image/jp2",
+       ".jpe":           "image/jpeg",
+       ".jpeg":          "image/jpeg",
+       ".jpg":           "image/jpeg",
+       ".kar":           "audio/midi",
+       ".lha":           "application/octet-stream",
+       ".lzh":           "application/octet-stream",
+       ".m4a":           "audio/mp4a-latm",
+       ".m4p":           "audio/mp4a-latm",
+       ".m4u":           "video/vnd.mpegurl",
+       ".m4v":           "video/x-m4v",
+       ".mac":           "image/x-macpaint",
+       ".mathml":        "application/mathml+xml",
+       ".mesh":          "model/mesh",
+       ".mid":           "audio/midi",
+       ".midi":          "audio/midi",
+       ".mov":           "video/quicktime",
+       ".mp2":           "audio/mpeg",
+       ".mp3":           "audio/mpeg",
+       ".mp4":           "video/mp4",
+       ".mpe":           "video/mpeg",
+       ".mpeg":          "video/mpeg",
+       ".mpg":           "video/mpeg",
+       ".mpga":          "audio/mpeg",
+       ".msh":           "model/mesh",
+       ".nc":            "application/x-netcdf",
+       ".oda":           "application/oda",
+       ".ogv":           "video/ogv",
+       ".pct":           "image/pict",
+       ".pic":           "image/pict",
+       ".pict":          "image/pict",
+       ".pnt":           "image/x-macpaint",
+       ".pntg":          "image/x-macpaint",
+       ".ps":            "application/postscript",
+       ".qt":            "video/quicktime",
+       ".qti":           "image/x-quicktime",
+       ".qtif":          "image/x-quicktime",
+       ".ram":           "audio/x-pn-realaudio",
+       ".rdf":           "application/rdf+xml",
+       ".rm":            "application/vnd.rn-realmedia",
+       ".roff":          "application/x-troff",
+       ".sgm":           "text/sgml",
+       ".sgml":          "text/sgml",
+       ".silo":          "model/mesh",
+       ".skd":           "application/x-koan",
+       ".skm":           "application/x-koan",
+       ".skp":           "application/x-koan",
+       ".skt":           "application/x-koan",
+       ".smi":           "application/smil",
+       ".smil":          "application/smil",
+       ".snd":           "audio/basic",
+       ".so":            "application/octet-stream",
+       ".svg":           "image/svg+xml",
+       ".t":             "application/x-troff",
+       ".texi":          "application/x-texinfo",
+       ".texinfo":       "application/x-texinfo",
+       ".tif":           "image/tiff",
+       ".tiff":          "image/tiff",
+       ".tr":            "application/x-troff",
+       ".txt":           "text/plain",
+       ".vrml":          "model/vrml",
+       ".vxml":          "application/voicexml+xml",
+       ".webm":          "video/webm",
+       ".wrl":           "model/vrml",
+       ".xht":           "application/xhtml+xml",
+       ".xhtml":         "application/xhtml+xml",
+       ".xml":           "application/xml",
+       ".xsl":           "application/xml",
+       ".xslt":          "application/xslt+xml",
+       ".xul":           "application/vnd.mozilla.xul+xml",
+       ".webp":          "image/webp",
+       ".323":           "text/h323",
+       ".aab":           "application/x-authoware-bin",
+       ".aam":           "application/x-authoware-map",
+       ".aas":           "application/x-authoware-seg",
+       ".acx":           "application/internet-property-stream",
+       ".als":           "audio/X-Alpha5",
+       ".amc":           "application/x-mpeg",
+       ".ani":           "application/octet-stream",
+       ".asd":           "application/astound",
+       ".asf":           "video/x-ms-asf",
+       ".asn":           "application/astound",
+       ".asp":           "application/x-asap",
+       ".asr":           "video/x-ms-asf",
+       ".asx":           "video/x-ms-asf",
+       ".avb":           "application/octet-stream",
+       ".awb":           "audio/amr-wb",
+       ".axs":           "application/olescript",
+       ".bas":           "text/plain",
+       ".bin ":          "application/octet-stream",
+       ".bld":           "application/bld",
+       ".bld2":          "application/bld2",
+       ".bpk":           "application/octet-stream",
+       ".c":             "text/plain",
+       ".cal":           "image/x-cals",
+       ".cat":           "application/vnd.ms-pkiseccat",
+       ".ccn":           "application/x-cnc",
+       ".cco":           "application/x-cocoa",
+       ".cer":           "application/x-x509-ca-cert",
+       ".cgi":           "magnus-internal/cgi",
+       ".chat":          "application/x-chat",
+       ".clp":           "application/x-msclip",
+       ".cmx":           "image/x-cmx",
+       ".co":            "application/x-cult3d-object",
+       ".cod":           "image/cis-cod",
+       ".conf":          "text/plain",
+       ".cpp":           "text/plain",
+       ".crd":           "application/x-mscardfile",
+       ".crl":           "application/pkix-crl",
+       ".crt":           "application/x-x509-ca-cert",
+       ".csm":           "chemical/x-csml",
+       ".csml":          "chemical/x-csml",
+       ".cur":           "application/octet-stream",
+       ".dcm":           "x-lml/x-evm",
+       ".dcx":           "image/x-dcx",
+       ".der":           "application/x-x509-ca-cert",
+       ".dhtml":         "text/html",
+       ".dot":           "application/msword",
+       ".dwf":           "drawing/x-dwf",
+       ".dwg":           "application/x-autocad",
+       ".dxf":           "application/x-autocad",
+       ".ebk":           "application/x-expandedbook",
+       ".emb":           "chemical/x-embl-dl-nucleotide",
+       ".embl":          "chemical/x-embl-dl-nucleotide",
+       ".epub":          "application/epub+zip",
+       ".eri":           "image/x-eri",
+       ".es":            "audio/echospeech",
+       ".esl":           "audio/echospeech",
+       ".etc":           "application/x-earthtime",
+       ".evm":           "x-lml/x-evm",
+       ".evy":           "application/envoy",
+       ".fh4":           "image/x-freehand",
+       ".fh5":           "image/x-freehand",
+       ".fhc":           "image/x-freehand",
+       ".fif":           "application/fractals",
+       ".flr":           "x-world/x-vrml",
+       ".fm":            "application/x-maker",
+       ".fpx":           "image/x-fpx",
+       ".fvi":           "video/isivideo",
+       ".gau":           "chemical/x-gaussian-input",
+       ".gca":           "application/x-gca-compressed",
+       ".gdb":           "x-lml/x-gdb",
+       ".gps":           "application/x-gps",
+       ".h":             "text/plain",
+       ".hdm":           "text/x-hdml",
+       ".hdml":          "text/x-hdml",
+       ".hlp":           "application/winhlp",
+       ".hta":           "application/hta",
+       ".htc":           "text/x-component",
+       ".hts":           "text/html",
+       ".htt":           "text/webviewhtml",
+       ".ifm":           "image/gif",
+       ".ifs":           "image/ifs",
+       ".iii":           "application/x-iphone",
+       ".imy":           "audio/melody",
+       ".ins":           "application/x-internet-signup",
+       ".ips":           "application/x-ipscript",
+       ".ipx":           "application/x-ipix",
+       ".isp":           "application/x-internet-signup",
+       ".it":            "audio/x-mod",
+       ".itz":           "audio/x-mod",
+       ".ivr":           "i-world/i-vrml",
+       ".j2k":           "image/j2k",
+       ".jam":           "application/x-jam",
+       ".java":          "text/plain",
+       ".jfif":          "image/pipeg",
+       ".jpz":           "image/jpeg",
+       ".jwc":           "application/jwc",
+       ".kjx":           "application/x-kjx",
+       ".lak":           "x-lml/x-lak",
+       ".lcc":           "application/fastman",
+       ".lcl":           "application/x-digitalloca",
+       ".lcr":           "application/x-digitalloca",
+       ".lgh":           "application/lgh",
+       ".lml":           "x-lml/x-lml",
+       ".lmlpack":       "x-lml/x-lmlpack",
+       ".log":           "text/plain",
+       ".lsf":           "video/x-la-asf",
+       ".lsx":           "video/x-la-asf",
+       ".m13":           "application/x-msmediaview",
+       ".m14":           "application/x-msmediaview",
+       ".m15":           "audio/x-mod",
+       ".m3url":         "audio/x-mpegurl",
+       ".m4b":           "audio/mp4a-latm",
+       ".ma1":           "audio/ma1",
+       ".ma2":           "audio/ma2",
+       ".ma3":           "audio/ma3",
+       ".ma5":           "audio/ma5",
+       ".map":           "magnus-internal/imagemap",
+       ".mbd":           "application/mbedlet",
+       ".mct":           "application/x-mascot",
+       ".mdb":           "application/x-msaccess",
+       ".mdz":           "audio/x-mod",
+       ".mel":           "text/x-vmel",
+       ".mht":           "message/rfc822",
+       ".mhtml":         "message/rfc822",
+       ".mi":            "application/x-mif",
+       ".mil":           "image/x-cals",
+       ".mio":           "audio/x-mio",
+       ".mmf":           "application/x-skt-lbs",
+       ".mng":           "video/x-mng",
+       ".mny":           "application/x-msmoney",
+       ".moc":           "application/x-mocha",
+       ".mocha":         "application/x-mocha",
+       ".mod":           "audio/x-mod",
+       ".mof":           "application/x-yumekara",
+       ".mol":           "chemical/x-mdl-molfile",
+       ".mop":           "chemical/x-mopac-input",
+       ".mpa":           "video/mpeg",
+       ".mpc":           "application/vnd.mpohun.certificate",
+       ".mpg4":          "video/mp4",
+       ".mpn":           "application/vnd.mophun.application",
+       ".mpp":           "application/vnd.ms-project",
+       ".mps":           "application/x-mapserver",
+       ".mpv2":          "video/mpeg",
+       ".mrl":           "text/x-mrml",
+       ".mrm":           "application/x-mrm",
+       ".msg":           "application/vnd.ms-outlook",
+       ".mts":           "application/metastream",
+       ".mtx":           "application/metastream",
+       ".mtz":           "application/metastream",
+       ".mvb":           "application/x-msmediaview",
+       ".mzv":           "application/metastream",
+       ".nar":           "application/zip",
+       ".nbmp":          "image/nbmp",
+       ".ndb":           "x-lml/x-ndb",
+       ".ndwn":          "application/ndwn",
+       ".nif":           "application/x-nif",
+       ".nmz":           "application/x-scream",
+       ".nokia-op-logo": "image/vnd.nok-oplogo-color",
+       ".npx":           "application/x-netfpx",
+       ".nsnd":          "audio/nsnd",
+       ".nva":           "application/x-neva1",
+       ".nws":           "message/rfc822",
+       ".oom":           "application/x-AtlasMate-Plugin",
+       ".p10":           "application/pkcs10",
+       ".p12":           "application/x-pkcs12",
+       ".p7b":           "application/x-pkcs7-certificates",
+       ".p7c":           "application/x-pkcs7-mime",
+       ".p7m":           "application/x-pkcs7-mime",
+       ".p7r":           "application/x-pkcs7-certreqresp",
+       ".p7s":           "application/x-pkcs7-signature",
+       ".pac":           "audio/x-pac",
+       ".pae":           "audio/x-epac",
+       ".pan":           "application/x-pan",
+       ".pcx":           "image/x-pcx",
+       ".pda":           "image/x-pda",
+       ".pfr":           "application/font-tdpfr",
+       ".pfx":           "application/x-pkcs12",
+       ".pko":           "application/ynd.ms-pkipko",
+       ".pm":            "application/x-perl",
+       ".pma":           "application/x-perfmon",
+       ".pmc":           "application/x-perfmon",
+       ".pmd":           "application/x-pmd",
+       ".pml":           "application/x-perfmon",
+       ".pmr":           "application/x-perfmon",
+       ".pmw":           "application/x-perfmon",
+       ".pnz":           "image/png",
+       ".pot,":          "application/vnd.ms-powerpoint",
+       ".pps":           "application/vnd.ms-powerpoint",
+       ".pqf":           "application/x-cprplayer",
+       ".pqi":           "application/cprplayer",
+       ".prc":           "application/x-prc",
+       ".prf":           "application/pics-rules",
+       ".prop":          "text/plain",
+       ".proxy":         "application/x-ns-proxy-autoconfig",
+       ".ptlk":          "application/listenup",
+       ".pub":           "application/x-mspublisher",
+       ".pvx":           "video/x-pv-pvx",
+       ".qcp":           "audio/vnd.qcelp",
+       ".r3t":           "text/vnd.rn-realtext3d",
+       ".rar":           "application/octet-stream",
+       ".rc":            "text/plain",
+       ".rf":            "image/vnd.rn-realflash",
+       ".rlf":           "application/x-richlink",
+       ".rmf":           "audio/x-rmf",
+       ".rmi":           "audio/mid",
+       ".rmm":           "audio/x-pn-realaudio",
+       ".rmvb":          "audio/x-pn-realaudio",
+       ".rnx":           "application/vnd.rn-realplayer",
+       ".rp":            "image/vnd.rn-realpix",
+       ".rt":            "text/vnd.rn-realtext",
+       ".rte":           "x-lml/x-gps",
+       ".rtg":           "application/metastream",
+       ".rv":            "video/vnd.rn-realvideo",
+       ".rwc":           "application/x-rogerwilco",
+       ".s3m":           "audio/x-mod",
+       ".s3z":           "audio/x-mod",
+       ".sca":           "application/x-supercard",
+       ".scd":           "application/x-msschedule",
+       ".sct":           "text/scriptlet",
+       ".sdf":           "application/e-score",
+       ".sea":           "application/x-stuffit",
+       ".setpay":        "application/set-payment-initiation",
+       ".setreg":        "application/set-registration-initiation",
+       ".shtml":         "text/html",
+       ".shtm":          "text/html",
+       ".shw":           "application/presentations",
+       ".si6":           "image/si6",
+       ".si7":           "image/vnd.stiwap.sis",
+       ".si9":           "image/vnd.lgtwap.sis",
+       ".slc":           "application/x-salsa",
+       ".smd":           "audio/x-smd",
+       ".smp":           "application/studiom",
+       ".smz":           "audio/x-smd",
+       ".spc":           "application/x-pkcs7-certificates",
+       ".spr":           "application/x-sprite",
+       ".sprite":        "application/x-sprite",
+       ".sdp":           "application/sdp",
+       ".spt":           "application/x-spt",
+       ".sst":           "application/vnd.ms-pkicertstore",
+       ".stk":           "application/hyperstudio",
+       ".stl":           "application/vnd.ms-pkistl",
+       ".stm":           "text/html",
+       ".svf":           "image/vnd",
+       ".svh":           "image/svh",
+       ".svr":           "x-world/x-svr",
+       ".swfl":          "application/x-shockwave-flash",
+       ".tad":           "application/octet-stream",
+       ".talk":          "text/x-speech",
+       ".taz":           "application/x-tar",
+       ".tbp":           "application/x-timbuktu",
+       ".tbt":           "application/x-timbuktu",
+       ".tgz":           "application/x-compressed",
+       ".thm":           "application/vnd.eri.thm",
+       ".tki":           "application/x-tkined",
+       ".tkined":        "application/x-tkined",
+       ".toc":           "application/toc",
+       ".toy":           "image/toy",
+       ".trk":           "x-lml/x-gps",
+       ".trm":           "application/x-msterminal",
+       ".tsi":           "audio/tsplayer",
+       ".tsp":           "application/dsptype",
+       ".ttf":           "application/octet-stream",
+       ".ttz":           "application/t-time",
+       ".uls":           "text/iuls",
+       ".ult":           "audio/x-mod",
+       ".uu":            "application/x-uuencode",
+       ".uue":           "application/x-uuencode",
+       ".vcf":           "text/x-vcard",
+       ".vdo":           "video/vdo",
+       ".vib":           "audio/vib",
+       ".viv":           "video/vivo",
+       ".vivo":          "video/vivo",
+       ".vmd":           "application/vocaltec-media-desc",
+       ".vmf":           "application/vocaltec-media-file",
+       ".vmi":           "application/x-dreamcast-vms-info",
+       ".vms":           "application/x-dreamcast-vms",
+       ".vox":           "audio/voxware",
+       ".vqe":           "audio/x-twinvq-plugin",
+       ".vqf":           "audio/x-twinvq",
+       ".vql":           "audio/x-twinvq",
+       ".vre":           "x-world/x-vream",
+       ".vrt":           "x-world/x-vrt",
+       ".vrw":           "x-world/x-vream",
+       ".vts":           "workbook/formulaone",
+       ".wcm":           "application/vnd.ms-works",
+       ".wdb":           "application/vnd.ms-works",
+       ".web":           "application/vnd.xara",
+       ".wi":            "image/wavelet",
+       ".wis":           "application/x-InstallShield",
+       ".wks":           "application/vnd.ms-works",
+       ".wmd":           "application/x-ms-wmd",
+       ".wmf":           "application/x-msmetafile",
+       ".wmlscript":     "text/vnd.wap.wmlscript",
+       ".wmz":           "application/x-ms-wmz",
+       ".wpng":          "image/x-up-wpng",
+       ".wps":           "application/vnd.ms-works",
+       ".wpt":           "x-lml/x-gps",
+       ".wri":           "application/x-mswrite",
+       ".wrz":           "x-world/x-vrml",
+       ".ws":            "text/vnd.wap.wmlscript",
+       ".wsc":           "application/vnd.wap.wmlscriptc",
+       ".wv":            "video/wavelet",
+       ".wxl":           "application/x-wxl",
+       ".x-gzip":        "application/x-gzip",
+       ".xaf":           "x-world/x-vrml",
+       ".xar":           "application/vnd.xara",
+       ".xdm":           "application/x-xdma",
+       ".xdma":          "application/x-xdma",
+       ".xdw":           "application/vnd.fujixerox.docuworks",
+       ".xhtm":          "application/xhtml+xml",
+       ".xla":           "application/vnd.ms-excel",
+       ".xlc":           "application/vnd.ms-excel",
+       ".xll":           "application/x-excel",
+       ".xlm":           "application/vnd.ms-excel",
+       ".xlt":           "application/vnd.ms-excel",
+       ".xlw":           "application/vnd.ms-excel",
+       ".xm":            "audio/x-mod",
+       ".xmz":           "audio/x-mod",
+       ".xof":           "x-world/x-vrml",
+       ".xpi":           "application/x-xpinstall",
+       ".xsit":          "text/xml",
+       ".yz1":           "application/x-yz1",
+       ".z":             "application/x-compress",
+       ".zac":           "application/x-zaurus-zac",
+       ".json":          "application/json",
+}
+
+// TypeByExtension returns the MIME type associated with the file extension ext.
+// gets the file's MIME type for HTTP header Content-Type
+func TypeByExtension(filePath string) string {
+       typ := mime.TypeByExtension(path.Ext(filePath))
+       if typ == "" {
+               typ = extToMimeType[strings.ToLower(path.Ext(filePath))]
+       }
+       return typ
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/model.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/model.go
new file mode 100644 (file)
index 0000000..b0b4a50
--- /dev/null
@@ -0,0 +1,69 @@
+package oss
+
+import (
+       "hash"
+       "io"
+       "net/http"
+)
+
+// Response defines HTTP response from OSS
+type Response struct {
+       StatusCode int
+       Headers    http.Header
+       Body       io.ReadCloser
+       ClientCRC  uint64
+       ServerCRC  uint64
+}
+
+func (r *Response) Read(p []byte) (n int, err error) {
+       return r.Body.Read(p)
+}
+
+// Close close http reponse body
+func (r *Response) Close() error {
+       return r.Body.Close()
+}
+
+// PutObjectRequest is the request of DoPutObject
+type PutObjectRequest struct {
+       ObjectKey string
+       Reader    io.Reader
+}
+
+// GetObjectRequest is the request of DoGetObject
+type GetObjectRequest struct {
+       ObjectKey string
+}
+
+// GetObjectResult is the result of DoGetObject
+type GetObjectResult struct {
+       Response  *Response
+       ClientCRC hash.Hash64
+       ServerCRC uint64
+}
+
+// AppendObjectRequest is the requtest of DoAppendObject
+type AppendObjectRequest struct {
+       ObjectKey string
+       Reader    io.Reader
+       Position  int64
+}
+
+// AppendObjectResult is the result of DoAppendObject
+type AppendObjectResult struct {
+       NextPosition int64
+       CRC          uint64
+}
+
+// UploadPartRequest is the request of DoUploadPart
+type UploadPartRequest struct {
+       InitResult *InitiateMultipartUploadResult
+       Reader     io.Reader
+       PartSize   int64
+       PartNumber int
+}
+
+// UploadPartResult is the result of DoUploadPart
+type UploadPartResult struct {
+       Part UploadPart
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy.go
new file mode 100644 (file)
index 0000000..56ed8ca
--- /dev/null
@@ -0,0 +1,474 @@
+package oss
+
+import (
+       "crypto/md5"
+       "encoding/base64"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "io/ioutil"
+       "net/http"
+       "os"
+       "strconv"
+)
+
+// CopyFile is multipart copy object
+//
+// srcBucketName    source bucket name
+// srcObjectKey    source object name
+// destObjectKey    target object name in the form of bucketname.objectkey
+// partSize    the part size in byte.
+// options    object's contraints. Check out function InitiateMultipartUpload.
+//
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) CopyFile(srcBucketName, srcObjectKey, destObjectKey string, partSize int64, options ...Option) error {
+       destBucketName := bucket.BucketName
+       if partSize < MinPartSize || partSize > MaxPartSize {
+               return errors.New("oss: part size invalid range (1024KB, 5GB]")
+       }
+
+       cpConf := getCpConfig(options)
+       routines := getRoutines(options)
+
+       var strVersionId string
+       versionId, _ := FindOption(options, "versionId", nil)
+       if versionId != nil {
+               strVersionId = versionId.(string)
+       }
+
+       if cpConf != nil && cpConf.IsEnable {
+               cpFilePath := getCopyCpFilePath(cpConf, srcBucketName, srcObjectKey, destBucketName, destObjectKey, strVersionId)
+               if cpFilePath != "" {
+                       return bucket.copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, destObjectKey, partSize, options, cpFilePath, routines)
+               }
+       }
+
+       return bucket.copyFile(srcBucketName, srcObjectKey, destBucketName, destObjectKey,
+               partSize, options, routines)
+}
+
+func getCopyCpFilePath(cpConf *cpConfig, srcBucket, srcObject, destBucket, destObject, versionId string) string {
+       if cpConf.FilePath == "" && cpConf.DirPath != "" {
+               dest := fmt.Sprintf("oss://%v/%v", destBucket, destObject)
+               src := fmt.Sprintf("oss://%v/%v", srcBucket, srcObject)
+               cpFileName := getCpFileName(src, dest, versionId)
+               cpConf.FilePath = cpConf.DirPath + string(os.PathSeparator) + cpFileName
+       }
+       return cpConf.FilePath
+}
+
+// ----- Concurrently copy without checkpoint ---------
+
+// copyWorkerArg defines the copy worker arguments
+type copyWorkerArg struct {
+       bucket        *Bucket
+       imur          InitiateMultipartUploadResult
+       srcBucketName string
+       srcObjectKey  string
+       options       []Option
+       hook          copyPartHook
+}
+
+// copyPartHook is the hook for testing purpose
+type copyPartHook func(part copyPart) error
+
+var copyPartHooker copyPartHook = defaultCopyPartHook
+
+func defaultCopyPartHook(part copyPart) error {
+       return nil
+}
+
+// copyWorker copies worker
+func copyWorker(id int, arg copyWorkerArg, jobs <-chan copyPart, results chan<- UploadPart, failed chan<- error, die <-chan bool) {
+       for chunk := range jobs {
+               if err := arg.hook(chunk); err != nil {
+                       failed <- err
+                       break
+               }
+               chunkSize := chunk.End - chunk.Start + 1
+               part, err := arg.bucket.UploadPartCopy(arg.imur, arg.srcBucketName, arg.srcObjectKey,
+                       chunk.Start, chunkSize, chunk.Number, arg.options...)
+               if err != nil {
+                       failed <- err
+                       break
+               }
+               select {
+               case <-die:
+                       return
+               default:
+               }
+               results <- part
+       }
+}
+
+// copyScheduler
+func copyScheduler(jobs chan copyPart, parts []copyPart) {
+       for _, part := range parts {
+               jobs <- part
+       }
+       close(jobs)
+}
+
+// copyPart structure
+type copyPart struct {
+       Number int   // Part number (from 1 to 10,000)
+       Start  int64 // The start index in the source file.
+       End    int64 // The end index in the source file
+}
+
+// getCopyParts calculates copy parts
+func getCopyParts(objectSize, partSize int64) []copyPart {
+       parts := []copyPart{}
+       part := copyPart{}
+       i := 0
+       for offset := int64(0); offset < objectSize; offset += partSize {
+               part.Number = i + 1
+               part.Start = offset
+               part.End = GetPartEnd(offset, objectSize, partSize)
+               parts = append(parts, part)
+               i++
+       }
+       return parts
+}
+
+// getSrcObjectBytes gets the source file size
+func getSrcObjectBytes(parts []copyPart) int64 {
+       var ob int64
+       for _, part := range parts {
+               ob += (part.End - part.Start + 1)
+       }
+       return ob
+}
+
+// copyFile is a concurrently copy without checkpoint
+func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destObjectKey string,
+       partSize int64, options []Option, routines int) error {
+       descBucket, err := bucket.Client.Bucket(destBucketName)
+       srcBucket, err := bucket.Client.Bucket(srcBucketName)
+       listener := GetProgressListener(options)
+
+       // choice valid options
+       headerOptions := ChoiceHeadObjectOption(options)
+       partOptions := ChoiceTransferPartOption(options)
+       completeOptions := ChoiceCompletePartOption(options)
+       abortOptions := ChoiceAbortPartOption(options)
+
+       meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, headerOptions...)
+       if err != nil {
+               return err
+       }
+
+       objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
+       if err != nil {
+               return err
+       }
+
+       // Get copy parts
+       parts := getCopyParts(objectSize, partSize)
+       // Initialize the multipart upload
+       imur, err := descBucket.InitiateMultipartUpload(destObjectKey, options...)
+       if err != nil {
+               return err
+       }
+
+       jobs := make(chan copyPart, len(parts))
+       results := make(chan UploadPart, len(parts))
+       failed := make(chan error)
+       die := make(chan bool)
+
+       var completedBytes int64
+       totalBytes := getSrcObjectBytes(parts)
+       event := newProgressEvent(TransferStartedEvent, 0, totalBytes, 0)
+       publishProgress(listener, event)
+
+       // Start to copy workers
+       arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, partOptions, copyPartHooker}
+       for w := 1; w <= routines; w++ {
+               go copyWorker(w, arg, jobs, results, failed, die)
+       }
+
+       // Start the scheduler
+       go copyScheduler(jobs, parts)
+
+       // Wait for the parts finished.
+       completed := 0
+       ups := make([]UploadPart, len(parts))
+       for completed < len(parts) {
+               select {
+               case part := <-results:
+                       completed++
+                       ups[part.PartNumber-1] = part
+                       copyBytes := (parts[part.PartNumber-1].End - parts[part.PartNumber-1].Start + 1)
+                       completedBytes += copyBytes
+                       event = newProgressEvent(TransferDataEvent, completedBytes, totalBytes, copyBytes)
+                       publishProgress(listener, event)
+               case err := <-failed:
+                       close(die)
+                       descBucket.AbortMultipartUpload(imur, abortOptions...)
+                       event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes, 0)
+                       publishProgress(listener, event)
+                       return err
+               }
+
+               if completed >= len(parts) {
+                       break
+               }
+       }
+
+       event = newProgressEvent(TransferCompletedEvent, completedBytes, totalBytes, 0)
+       publishProgress(listener, event)
+
+       // Complete the multipart upload
+       _, err = descBucket.CompleteMultipartUpload(imur, ups, completeOptions...)
+       if err != nil {
+               bucket.AbortMultipartUpload(imur, abortOptions...)
+               return err
+       }
+       return nil
+}
+
+// ----- Concurrently copy with checkpoint  -----
+
+const copyCpMagic = "84F1F18C-FF1D-403B-A1D8-9DEB5F65910A"
+
+type copyCheckpoint struct {
+       Magic          string       // Magic
+       MD5            string       // CP content MD5
+       SrcBucketName  string       // Source bucket
+       SrcObjectKey   string       // Source object
+       DestBucketName string       // Target bucket
+       DestObjectKey  string       // Target object
+       CopyID         string       // Copy ID
+       ObjStat        objectStat   // Object stat
+       Parts          []copyPart   // Copy parts
+       CopyParts      []UploadPart // The uploaded parts
+       PartStat       []bool       // The part status
+}
+
+// isValid checks if the data is valid which means CP is valid and object is not updated.
+func (cp copyCheckpoint) isValid(meta http.Header) (bool, error) {
+       // Compare CP's magic number and the MD5.
+       cpb := cp
+       cpb.MD5 = ""
+       js, _ := json.Marshal(cpb)
+       sum := md5.Sum(js)
+       b64 := base64.StdEncoding.EncodeToString(sum[:])
+
+       if cp.Magic != downloadCpMagic || b64 != cp.MD5 {
+               return false, nil
+       }
+
+       objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 64)
+       if err != nil {
+               return false, err
+       }
+
+       // Compare the object size and last modified time and etag.
+       if cp.ObjStat.Size != objectSize ||
+               cp.ObjStat.LastModified != meta.Get(HTTPHeaderLastModified) ||
+               cp.ObjStat.Etag != meta.Get(HTTPHeaderEtag) {
+               return false, nil
+       }
+
+       return true, nil
+}
+
+// load loads from the checkpoint file
+func (cp *copyCheckpoint) load(filePath string) error {
+       contents, err := ioutil.ReadFile(filePath)
+       if err != nil {
+               return err
+       }
+
+       err = json.Unmarshal(contents, cp)
+       return err
+}
+
+// update updates the parts status
+func (cp *copyCheckpoint) update(part UploadPart) {
+       cp.CopyParts[part.PartNumber-1] = part
+       cp.PartStat[part.PartNumber-1] = true
+}
+
+// dump dumps the CP to the file
+func (cp *copyCheckpoint) dump(filePath string) error {
+       bcp := *cp
+
+       // Calculate MD5
+       bcp.MD5 = ""
+       js, err := json.Marshal(bcp)
+       if err != nil {
+               return err
+       }
+       sum := md5.Sum(js)
+       b64 := base64.StdEncoding.EncodeToString(sum[:])
+       bcp.MD5 = b64
+
+       // Serialization
+       js, err = json.Marshal(bcp)
+       if err != nil {
+               return err
+       }
+
+       // Dump
+       return ioutil.WriteFile(filePath, js, FilePermMode)
+}
+
+// todoParts returns unfinished parts
+func (cp copyCheckpoint) todoParts() []copyPart {
+       dps := []copyPart{}
+       for i, ps := range cp.PartStat {
+               if !ps {
+                       dps = append(dps, cp.Parts[i])
+               }
+       }
+       return dps
+}
+
+// getCompletedBytes returns finished bytes count
+func (cp copyCheckpoint) getCompletedBytes() int64 {
+       var completedBytes int64
+       for i, part := range cp.Parts {
+               if cp.PartStat[i] {
+                       completedBytes += (part.End - part.Start + 1)
+               }
+       }
+       return completedBytes
+}
+
+// prepare initializes the multipart upload
+func (cp *copyCheckpoint) prepare(meta http.Header, srcBucket *Bucket, srcObjectKey string, destBucket *Bucket, destObjectKey string,
+       partSize int64, options []Option) error {
+       // CP
+       cp.Magic = copyCpMagic
+       cp.SrcBucketName = srcBucket.BucketName
+       cp.SrcObjectKey = srcObjectKey
+       cp.DestBucketName = destBucket.BucketName
+       cp.DestObjectKey = destObjectKey
+
+       objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 64)
+       if err != nil {
+               return err
+       }
+
+       cp.ObjStat.Size = objectSize
+       cp.ObjStat.LastModified = meta.Get(HTTPHeaderLastModified)
+       cp.ObjStat.Etag = meta.Get(HTTPHeaderEtag)
+
+       // Parts
+       cp.Parts = getCopyParts(objectSize, partSize)
+       cp.PartStat = make([]bool, len(cp.Parts))
+       for i := range cp.PartStat {
+               cp.PartStat[i] = false
+       }
+       cp.CopyParts = make([]UploadPart, len(cp.Parts))
+
+       // Init copy
+       imur, err := destBucket.InitiateMultipartUpload(destObjectKey, options...)
+       if err != nil {
+               return err
+       }
+       cp.CopyID = imur.UploadID
+
+       return nil
+}
+
+func (cp *copyCheckpoint) complete(bucket *Bucket, parts []UploadPart, cpFilePath string, options []Option) error {
+       imur := InitiateMultipartUploadResult{Bucket: cp.DestBucketName,
+               Key: cp.DestObjectKey, UploadID: cp.CopyID}
+       _, err := bucket.CompleteMultipartUpload(imur, parts, options...)
+       if err != nil {
+               return err
+       }
+       os.Remove(cpFilePath)
+       return err
+}
+
+// copyFileWithCp is concurrently copy with checkpoint
+func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, destObjectKey string,
+       partSize int64, options []Option, cpFilePath string, routines int) error {
+       descBucket, err := bucket.Client.Bucket(destBucketName)
+       srcBucket, err := bucket.Client.Bucket(srcBucketName)
+       listener := GetProgressListener(options)
+
+       // Load CP data
+       ccp := copyCheckpoint{}
+       err = ccp.load(cpFilePath)
+       if err != nil {
+               os.Remove(cpFilePath)
+       }
+
+       // choice valid options
+       headerOptions := ChoiceHeadObjectOption(options)
+       partOptions := ChoiceTransferPartOption(options)
+       completeOptions := ChoiceCompletePartOption(options)
+
+       meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, headerOptions...)
+       if err != nil {
+               return err
+       }
+
+       // Load error or the CP data is invalid---reinitialize
+       valid, err := ccp.isValid(meta)
+       if err != nil || !valid {
+               if err = ccp.prepare(meta, srcBucket, srcObjectKey, descBucket, destObjectKey, partSize, options); err != nil {
+                       return err
+               }
+               os.Remove(cpFilePath)
+       }
+
+       // Unfinished parts
+       parts := ccp.todoParts()
+       imur := InitiateMultipartUploadResult{
+               Bucket:   destBucketName,
+               Key:      destObjectKey,
+               UploadID: ccp.CopyID}
+
+       jobs := make(chan copyPart, len(parts))
+       results := make(chan UploadPart, len(parts))
+       failed := make(chan error)
+       die := make(chan bool)
+
+       completedBytes := ccp.getCompletedBytes()
+       event := newProgressEvent(TransferStartedEvent, completedBytes, ccp.ObjStat.Size, 0)
+       publishProgress(listener, event)
+
+       // Start the worker coroutines
+       arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, partOptions, copyPartHooker}
+       for w := 1; w <= routines; w++ {
+               go copyWorker(w, arg, jobs, results, failed, die)
+       }
+
+       // Start the scheduler
+       go copyScheduler(jobs, parts)
+
+       // Wait for the parts completed.
+       completed := 0
+       for completed < len(parts) {
+               select {
+               case part := <-results:
+                       completed++
+                       ccp.update(part)
+                       ccp.dump(cpFilePath)
+                       copyBytes := (parts[part.PartNumber-1].End - parts[part.PartNumber-1].Start + 1)
+                       completedBytes += copyBytes
+                       event = newProgressEvent(TransferDataEvent, completedBytes, ccp.ObjStat.Size, copyBytes)
+                       publishProgress(listener, event)
+               case err := <-failed:
+                       close(die)
+                       event = newProgressEvent(TransferFailedEvent, completedBytes, ccp.ObjStat.Size, 0)
+                       publishProgress(listener, event)
+                       return err
+               }
+
+               if completed >= len(parts) {
+                       break
+               }
+       }
+
+       event = newProgressEvent(TransferCompletedEvent, completedBytes, ccp.ObjStat.Size, 0)
+       publishProgress(listener, event)
+
+       return ccp.complete(descBucket, ccp.CopyParts, cpFilePath, completeOptions)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy_test.go
new file mode 100644 (file)
index 0000000..4742a9b
--- /dev/null
@@ -0,0 +1,669 @@
+package oss
+
+import (
+       "fmt"
+       "net/http"
+       "os"
+       "strings"
+       "time"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssCopySuite struct {
+       client *Client
+       bucket *Bucket
+}
+
+var _ = Suite(&OssCopySuite{})
+
+// SetUpSuite runs once when the suite starts running
+func (s *OssCopySuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       s.client = client
+
+       s.client.CreateBucket(bucketName)
+
+       bucket, err := s.client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       s.bucket = bucket
+
+       testLogger.Println("test copy started")
+}
+
+// TearDownSuite runs before each test or benchmark starts running
+func (s *OssCopySuite) TearDownSuite(c *C) {
+       // Delete Part
+       keyMarker := KeyMarker("")
+       uploadIDMarker := UploadIDMarker("")
+       for {
+               lmur, err := s.bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+               c.Assert(err, IsNil)
+               for _, upload := range lmur.Uploads {
+                       var imur = InitiateMultipartUploadResult{Bucket: bucketName,
+                               Key: upload.Key, UploadID: upload.UploadID}
+                       err = s.bucket.AbortMultipartUpload(imur)
+                       c.Assert(err, IsNil)
+               }
+               keyMarker = KeyMarker(lmur.NextKeyMarker)
+               uploadIDMarker = UploadIDMarker(lmur.NextUploadIDMarker)
+               if !lmur.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete objects
+       marker := Marker("")
+       for {
+               lor, err := s.bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = s.bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete bucket
+       err := s.client.DeleteBucket(s.bucket.BucketName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("test copy completed")
+}
+
+// SetUpTest runs after each test or benchmark runs
+func (s *OssCopySuite) SetUpTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+}
+
+// TearDownTest runs once after all tests or benchmarks have finished running
+func (s *OssCopySuite) TearDownTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+}
+
+// TestCopyRoutineWithoutRecovery is multi-routine copy without resumable recovery
+func (s *OssCopySuite) TestCopyRoutineWithoutRecovery(c *C) {
+       srcObjectName := objectNamePrefix + RandStr(8)
+       destObjectName := srcObjectName + "-dest"
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := "copy-new-file.jpg"
+
+       // Upload source file
+       err := s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Does not specify parameter 'routines', by default it's single routine
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Specify one routine.
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(1))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Specify three routines, which is less than parts count 5
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Specify 5 routines which is the same as parts count
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(5))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Specify routine count 10, which is more than parts count
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(10))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Invalid routine count, will use single routine
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(-1))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Option
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(3), Meta("myprop", "mypropval"))
+
+       meta, err := s.bucket.GetObjectDetailedMeta(destObjectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       err = s.bucket.DeleteObject(srcObjectName)
+       c.Assert(err, IsNil)
+}
+
+// CopyErrorHooker is a copypart request hook
+func CopyErrorHooker(part copyPart) error {
+       if part.Number == 5 {
+               time.Sleep(time.Second)
+               return fmt.Errorf("ErrorHooker")
+       }
+       return nil
+}
+
+// TestCopyRoutineWithoutRecoveryNegative is a multiple routines copy without checkpoint
+func (s *OssCopySuite) TestCopyRoutineWithoutRecoveryNegative(c *C) {
+       srcObjectName := objectNamePrefix + RandStr(8)
+       destObjectName := srcObjectName + "-dest"
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+
+       // Upload source file
+       err := s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       copyPartHooker = CopyErrorHooker
+       // Worker routine errors
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(2))
+
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       copyPartHooker = defaultCopyPartHook
+
+       // Source bucket does not exist
+       err = s.bucket.CopyFile("notexist", srcObjectName, destObjectName, 100*1024, Routines(2))
+       c.Assert(err, NotNil)
+
+       // Target object does not exist
+       err = s.bucket.CopyFile(bucketName, "notexist", destObjectName, 100*1024, Routines(2))
+
+       // The part size is invalid
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024, Routines(2))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*1024*1024*100, Routines(2))
+       c.Assert(err, NotNil)
+
+       // Delete the source file
+       err = s.bucket.DeleteObject(srcObjectName)
+       c.Assert(err, IsNil)
+}
+
+// TestCopyRoutineWithRecovery is a multiple routines copy with resumable recovery
+func (s *OssCopySuite) TestCopyRoutineWithRecovery(c *C) {
+       srcObjectName := objectNamePrefix + RandStr(8)
+       destObjectName := srcObjectName + "-dest"
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       // Upload source file
+       err := s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Routines default value, CP's default path is destObjectName+.cp
+       // Copy object with checkpoint enabled, single runtine.
+       // Copy 4 parts---the CopyErrorHooker makes sure the copy of part 5 will fail.
+       copyPartHooker = CopyErrorHooker
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Checkpoint(true, destObjectName+".cp"))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       copyPartHooker = defaultCopyPartHook
+
+       // Check CP
+       ccp := copyCheckpoint{}
+       err = ccp.load(destObjectName + ".cp")
+       c.Assert(err, IsNil)
+       c.Assert(ccp.Magic, Equals, copyCpMagic)
+       c.Assert(len(ccp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ=="))
+       c.Assert(ccp.SrcBucketName, Equals, bucketName)
+       c.Assert(ccp.SrcObjectKey, Equals, srcObjectName)
+       c.Assert(ccp.DestBucketName, Equals, bucketName)
+       c.Assert(ccp.DestObjectKey, Equals, destObjectName)
+       c.Assert(len(ccp.CopyID), Equals, len("3F79722737D1469980DACEDCA325BB52"))
+       c.Assert(ccp.ObjStat.Size, Equals, int64(482048))
+       c.Assert(len(ccp.ObjStat.LastModified), Equals, len("2015-12-17 18:43:03 +0800 CST"))
+       c.Assert(ccp.ObjStat.Etag, Equals, "\"2351E662233817A7AE974D8C5B0876DD-5\"")
+       c.Assert(len(ccp.Parts), Equals, 5)
+       c.Assert(len(ccp.todoParts()), Equals, 1)
+       c.Assert(ccp.PartStat[4], Equals, false)
+
+       // Second copy, finish the last part
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Checkpoint(true, destObjectName+".cp"))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       err = ccp.load(fileName + ".cp")
+       c.Assert(err, NotNil)
+
+       //multicopy with empty checkpoint path
+       copyPartHooker = CopyErrorHooker
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Checkpoint(true, ""))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       copyPartHooker = defaultCopyPartHook
+       ccp = copyCheckpoint{}
+       err = ccp.load(destObjectName + ".cp")
+       c.Assert(err, NotNil)
+
+       //multi copy with checkpoint dir
+       copyPartHooker = CopyErrorHooker
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(2), CheckpointDir(true, "./"))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       copyPartHooker = defaultCopyPartHook
+
+       // Check CP
+       ccp = copyCheckpoint{}
+       cpConf := cpConfig{IsEnable: true, DirPath: "./"}
+       cpFilePath := getCopyCpFilePath(&cpConf, bucketName, srcObjectName, s.bucket.BucketName, destObjectName, "")
+       err = ccp.load(cpFilePath)
+       c.Assert(err, IsNil)
+       c.Assert(ccp.Magic, Equals, copyCpMagic)
+       c.Assert(len(ccp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ=="))
+       c.Assert(ccp.SrcBucketName, Equals, bucketName)
+       c.Assert(ccp.SrcObjectKey, Equals, srcObjectName)
+       c.Assert(ccp.DestBucketName, Equals, bucketName)
+       c.Assert(ccp.DestObjectKey, Equals, destObjectName)
+       c.Assert(len(ccp.CopyID), Equals, len("3F79722737D1469980DACEDCA325BB52"))
+       c.Assert(ccp.ObjStat.Size, Equals, int64(482048))
+       c.Assert(len(ccp.ObjStat.LastModified), Equals, len("2015-12-17 18:43:03 +0800 CST"))
+       c.Assert(ccp.ObjStat.Etag, Equals, "\"2351E662233817A7AE974D8C5B0876DD-5\"")
+       c.Assert(len(ccp.Parts), Equals, 5)
+       c.Assert(len(ccp.todoParts()), Equals, 1)
+       c.Assert(ccp.PartStat[4], Equals, false)
+
+       // Second copy, finish the last part.
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(2), CheckpointDir(true, "./"))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       err = ccp.load(srcObjectName + ".cp")
+       c.Assert(err, NotNil)
+
+       // First copy without error.
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(3), Checkpoint(true, destObjectName+".cp"))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Copy with multiple coroutines, no errors.
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(10), Checkpoint(true, destObjectName+".cp"))
+       c.Assert(err, IsNil)
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Option
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(5), Checkpoint(true, destObjectName+".cp"), Meta("myprop", "mypropval"))
+       c.Assert(err, IsNil)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(destObjectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       err = s.bucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Delete the source file
+       err = s.bucket.DeleteObject(srcObjectName)
+       c.Assert(err, IsNil)
+}
+
+// TestCopyRoutineWithRecoveryNegative is a multiple routineed copy without checkpoint
+func (s *OssCopySuite) TestCopyRoutineWithRecoveryNegative(c *C) {
+       srcObjectName := objectNamePrefix + RandStr(8)
+       destObjectName := srcObjectName + "-dest"
+
+       // Source bucket does not exist
+       err := s.bucket.CopyFile("notexist", srcObjectName, destObjectName, 100*1024, Checkpoint(true, destObjectName+".cp"))
+       c.Assert(err, NotNil)
+       c.Assert(err, NotNil)
+
+       // Source object does not exist
+       err = s.bucket.CopyFile(bucketName, "notexist", destObjectName, 100*1024, Routines(2), Checkpoint(true, destObjectName+".cp"))
+       c.Assert(err, NotNil)
+
+       // Specify part size is invalid.
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024, Checkpoint(true, destObjectName+".cp"))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*1024*1024*100, Routines(2), Checkpoint(true, destObjectName+".cp"))
+       c.Assert(err, NotNil)
+}
+
+// TestCopyFileCrossBucket is a cross bucket's direct copy.
+func (s *OssCopySuite) TestCopyFileCrossBucket(c *C) {
+       destBucketName := bucketName + "-desc"
+       srcObjectName := objectNamePrefix + RandStr(8)
+       destObjectName := srcObjectName + "-dest"
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       destBucket, err := s.client.Bucket(destBucketName)
+       c.Assert(err, IsNil)
+
+       // Create a target bucket
+       err = s.client.CreateBucket(destBucketName)
+
+       // Upload source file
+       err = s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Copy files
+       err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(5), Checkpoint(true, destObjectName+".cp"))
+       c.Assert(err, IsNil)
+
+       err = destBucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = destBucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Copy file with options
+       err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(10), Checkpoint(true, "copy.cp"), Meta("myprop", "mypropval"))
+       c.Assert(err, IsNil)
+
+       err = destBucket.GetObjectToFile(destObjectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = destBucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Delete target bucket
+       ForceDeleteBucket(s.client, destBucketName, c)
+}
+
+func (s *OssCopySuite) TestVersioningCopyFileCrossBucket(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // begin test
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "test-file-" + RandStr(8)
+       fileData := RandStr(500 * 1024)
+       CreateFile(fileName, fileData, c)
+       newFile := "test-file-" + RandStr(8)
+       destBucketName := bucketName + "-desc"
+       srcObjectName := objectNamePrefix + RandStr(8)
+       destObjectName := srcObjectName + "-dest"
+
+       // Create dest bucket
+       err = client.CreateBucket(destBucketName)
+       c.Assert(err, IsNil)
+       destBucket, err := client.Bucket(destBucketName)
+       c.Assert(err, IsNil)
+
+       err = client.SetBucketVersioning(destBucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // Upload source file
+       var respHeader http.Header
+       options := []Option{Routines(3), GetResponseHeader(&respHeader)}
+       err = bucket.UploadFile(srcObjectName, fileName, 100*1024, options...)
+       versionId := GetVersionId(respHeader)
+       c.Assert(len(versionId) > 0, Equals, true)
+
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // overwrite emtpy object
+       err = bucket.PutObject(srcObjectName, strings.NewReader(""))
+       c.Assert(err, IsNil)
+
+       // Copy files
+       var respCopyHeader http.Header
+       options = []Option{Routines(5), Checkpoint(true, destObjectName+".cp"), GetResponseHeader(&respCopyHeader), VersionId(versionId)}
+       err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, options...)
+       c.Assert(err, IsNil)
+       versionIdCopy := GetVersionId(respCopyHeader)
+       c.Assert(len(versionIdCopy) > 0, Equals, true)
+
+       err = destBucket.GetObjectToFile(destObjectName, newFile, VersionId(versionIdCopy))
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = destBucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Copy file with options meta
+       options = []Option{Routines(10), Checkpoint(true, "copy.cp"), Meta("myprop", "mypropval"), GetResponseHeader(&respCopyHeader), VersionId(versionId)}
+       err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, options...)
+       c.Assert(err, IsNil)
+       versionIdCopy = GetVersionId(respCopyHeader)
+
+       err = destBucket.GetObjectToFile(destObjectName, newFile, VersionId(versionIdCopy))
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(fileName)
+       os.Remove(newFile)
+       destBucket.DeleteObject(destObjectName)
+       bucket.DeleteObject(objectName)
+       ForceDeleteBucket(client, bucketName, c)
+       ForceDeleteBucket(client, destBucketName, c)
+}
+
+// TestCopyFileChoiceOptions
+func (s *OssCopySuite) TestCopyFileChoiceOptions(c *C) {
+       destBucketName := bucketName + "-desc"
+       srcObjectName := objectNamePrefix + RandStr(8)
+       destObjectName := srcObjectName + "-dest"
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       destBucket, err := s.client.Bucket(destBucketName)
+       c.Assert(err, IsNil)
+
+       // Create a target bucket
+       err = s.client.CreateBucket(destBucketName)
+
+       // Upload source file
+       err = s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // copyfile with properties
+       options := []Option{
+               ObjectACL(ACLPublicRead),
+               RequestPayer(Requester),
+               TrafficLimitHeader(1024 * 1024 * 8),
+               ObjectStorageClass(StorageArchive),
+               ServerSideEncryption("AES256"),
+               Routines(5), // without checkpoint
+       }
+
+       // Copy files
+       err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, options...)
+       c.Assert(err, IsNil)
+
+       // check object
+       meta, err := destBucket.GetObjectDetailedMeta(destObjectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Storage-Class"), Equals, "Archive")
+       c.Assert(meta.Get("X-Oss-Server-Side-Encryption"), Equals, "AES256")
+
+       aclResult, err := destBucket.GetObjectACL(destObjectName)
+       c.Assert(aclResult.ACL, Equals, "public-read")
+       c.Assert(err, IsNil)
+
+       err = destBucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Copy file with options
+       options = []Option{
+               ObjectACL(ACLPublicRead),
+               RequestPayer(Requester),
+               TrafficLimitHeader(1024 * 1024 * 8),
+               ObjectStorageClass(StorageArchive),
+               ServerSideEncryption("AES256"),
+               Routines(10),
+               Checkpoint(true, "copy.cp"), // with checkpoint
+       }
+
+       err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, options...)
+       c.Assert(err, IsNil)
+
+       // check object
+       meta, err = destBucket.GetObjectDetailedMeta(destObjectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Storage-Class"), Equals, "Archive")
+       c.Assert(meta.Get("X-Oss-Server-Side-Encryption"), Equals, "AES256")
+
+       aclResult, err = destBucket.GetObjectACL(destObjectName)
+       c.Assert(aclResult.ACL, Equals, "public-read")
+       c.Assert(err, IsNil)
+
+       err = destBucket.DeleteObject(destObjectName)
+       c.Assert(err, IsNil)
+       os.Remove(newFile)
+
+       // Delete target bucket
+       err = s.client.DeleteBucket(destBucketName)
+       c.Assert(err, IsNil)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart.go
new file mode 100644 (file)
index 0000000..eed490a
--- /dev/null
@@ -0,0 +1,308 @@
+package oss
+
+import (
+       "bytes"
+       "encoding/xml"
+       "io"
+       "net/http"
+       "net/url"
+       "os"
+       "sort"
+       "strconv"
+)
+
+// InitiateMultipartUpload initializes multipart upload
+//
+// objectKey    object name
+// options    the object constricts for upload. The valid options are CacheControl, ContentDisposition, ContentEncoding, Expires,
+//            ServerSideEncryption, Meta, check out the following link:
+//            https://help.aliyun.com/document_detail/oss/api-reference/multipart-upload/InitiateMultipartUpload.html
+//
+// InitiateMultipartUploadResult    the return value of the InitiateMultipartUpload, which is used for calls later on such as UploadPartFromFile,UploadPartCopy.
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) InitiateMultipartUpload(objectKey string, options ...Option) (InitiateMultipartUploadResult, error) {
+       var imur InitiateMultipartUploadResult
+       opts := AddContentType(options, objectKey)
+       params, _ := GetRawParams(options)
+       _, ok := params["sequential"]
+       if ok {
+               // convert "" to nil
+               params["sequential"] = nil
+       }
+       params["uploads"] = nil
+
+       resp, err := bucket.do("POST", objectKey, params, opts, nil, nil)
+       if err != nil {
+               return imur, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &imur)
+       return imur, err
+}
+
+// UploadPart uploads parts
+//
+// After initializing a Multipart Upload, the upload Id and object key could be used for uploading the parts.
+// Each part has its part number (ranges from 1 to 10,000). And for each upload Id, the part number identifies the position of the part in the whole file.
+// And thus with the same part number and upload Id, another part upload will overwrite the data.
+// Except the last one, minimal part size is 100KB. There's no limit on the last part size.
+//
+// imur    the returned value of InitiateMultipartUpload.
+// reader    io.Reader the reader for the part's data.
+// size    the part size.
+// partNumber    the part number (ranges from 1 to 10,000). Invalid part number will lead to InvalidArgument error.
+//
+// UploadPart    the return value of the upload part. It consists of PartNumber and ETag. It's valid when error is nil.
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) UploadPart(imur InitiateMultipartUploadResult, reader io.Reader,
+       partSize int64, partNumber int, options ...Option) (UploadPart, error) {
+       request := &UploadPartRequest{
+               InitResult: &imur,
+               Reader:     reader,
+               PartSize:   partSize,
+               PartNumber: partNumber,
+       }
+
+       result, err := bucket.DoUploadPart(request, options)
+
+       return result.Part, err
+}
+
+// UploadPartFromFile uploads part from the file.
+//
+// imur    the return value of a successful InitiateMultipartUpload.
+// filePath    the local file path to upload.
+// startPosition    the start position in the local file.
+// partSize    the part size.
+// partNumber    the part number (from 1 to 10,000)
+//
+// UploadPart    the return value consists of PartNumber and ETag.
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) UploadPartFromFile(imur InitiateMultipartUploadResult, filePath string,
+       startPosition, partSize int64, partNumber int, options ...Option) (UploadPart, error) {
+       var part = UploadPart{}
+       fd, err := os.Open(filePath)
+       if err != nil {
+               return part, err
+       }
+       defer fd.Close()
+       fd.Seek(startPosition, os.SEEK_SET)
+
+       request := &UploadPartRequest{
+               InitResult: &imur,
+               Reader:     fd,
+               PartSize:   partSize,
+               PartNumber: partNumber,
+       }
+
+       result, err := bucket.DoUploadPart(request, options)
+
+       return result.Part, err
+}
+
+// DoUploadPart does the actual part upload.
+//
+// request    part upload request
+//
+// UploadPartResult    the result of uploading part.
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) DoUploadPart(request *UploadPartRequest, options []Option) (*UploadPartResult, error) {
+       listener := GetProgressListener(options)
+       options = append(options, ContentLength(request.PartSize))
+       params := map[string]interface{}{}
+       params["partNumber"] = strconv.Itoa(request.PartNumber)
+       params["uploadId"] = request.InitResult.UploadID
+       resp, err := bucket.do("PUT", request.InitResult.Key, params, options,
+               &io.LimitedReader{R: request.Reader, N: request.PartSize}, listener)
+       if err != nil {
+               return &UploadPartResult{}, err
+       }
+       defer resp.Body.Close()
+
+       part := UploadPart{
+               ETag:       resp.Headers.Get(HTTPHeaderEtag),
+               PartNumber: request.PartNumber,
+       }
+
+       if bucket.GetConfig().IsEnableCRC {
+               err = CheckCRC(resp, "DoUploadPart")
+               if err != nil {
+                       return &UploadPartResult{part}, err
+               }
+       }
+
+       return &UploadPartResult{part}, nil
+}
+
+// UploadPartCopy uploads part copy
+//
+// imur    the return value of InitiateMultipartUpload
+// copySrc    source Object name
+// startPosition    the part's start index in the source file
+// partSize    the part size
+// partNumber    the part number, ranges from 1 to 10,000. If it exceeds the range OSS returns InvalidArgument error.
+// options    the constraints of source object for the copy. The copy happens only when these contraints are met. Otherwise it returns error.
+//            CopySourceIfNoneMatch, CopySourceIfModifiedSince  CopySourceIfUnmodifiedSince, check out the following link for the detail
+//            https://help.aliyun.com/document_detail/oss/api-reference/multipart-upload/UploadPartCopy.html
+//
+// UploadPart    the return value consists of PartNumber and ETag.
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) UploadPartCopy(imur InitiateMultipartUploadResult, srcBucketName, srcObjectKey string,
+       startPosition, partSize int64, partNumber int, options ...Option) (UploadPart, error) {
+       var out UploadPartCopyResult
+       var part UploadPart
+       var opts []Option
+
+       //first find version id
+       versionIdKey := "versionId"
+       versionId, _ := FindOption(options, versionIdKey, nil)
+       if versionId == nil {
+               opts = []Option{CopySource(srcBucketName, url.QueryEscape(srcObjectKey)),
+                       CopySourceRange(startPosition, partSize)}
+       } else {
+               opts = []Option{CopySourceVersion(srcBucketName, url.QueryEscape(srcObjectKey), versionId.(string)),
+                       CopySourceRange(startPosition, partSize)}
+               options = DeleteOption(options, versionIdKey)
+       }
+
+       opts = append(opts, options...)
+
+       params := map[string]interface{}{}
+       params["partNumber"] = strconv.Itoa(partNumber)
+       params["uploadId"] = imur.UploadID
+       resp, err := bucket.do("PUT", imur.Key, params, opts, nil, nil)
+       if err != nil {
+               return part, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       if err != nil {
+               return part, err
+       }
+       part.ETag = out.ETag
+       part.PartNumber = partNumber
+
+       return part, nil
+}
+
+// CompleteMultipartUpload completes the multipart upload.
+//
+// imur    the return value of InitiateMultipartUpload.
+// parts    the array of return value of UploadPart/UploadPartFromFile/UploadPartCopy.
+//
+// CompleteMultipartUploadResponse    the return value when the call succeeds. Only valid when the error is nil.
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) CompleteMultipartUpload(imur InitiateMultipartUploadResult,
+       parts []UploadPart, options ...Option) (CompleteMultipartUploadResult, error) {
+       var out CompleteMultipartUploadResult
+
+       sort.Sort(UploadParts(parts))
+       cxml := completeMultipartUploadXML{}
+       cxml.Part = parts
+       bs, err := xml.Marshal(cxml)
+       if err != nil {
+               return out, err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       params := map[string]interface{}{}
+       params["uploadId"] = imur.UploadID
+       resp, err := bucket.do("POST", imur.Key, params, options, buffer, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       return out, err
+}
+
+// AbortMultipartUpload aborts the multipart upload.
+//
+// imur    the return value of InitiateMultipartUpload.
+//
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) AbortMultipartUpload(imur InitiateMultipartUploadResult, options ...Option) error {
+       params := map[string]interface{}{}
+       params["uploadId"] = imur.UploadID
+       resp, err := bucket.do("DELETE", imur.Key, params, options, nil, nil)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+// ListUploadedParts lists the uploaded parts.
+//
+// imur    the return value of InitiateMultipartUpload.
+//
+// ListUploadedPartsResponse    the return value if it succeeds, only valid when error is nil.
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) ListUploadedParts(imur InitiateMultipartUploadResult, options ...Option) (ListUploadedPartsResult, error) {
+       var out ListUploadedPartsResult
+       options = append(options, EncodingType("url"))
+
+       params := map[string]interface{}{}
+       params, err := GetRawParams(options)
+       if err != nil {
+               return out, err
+       }
+
+       params["uploadId"] = imur.UploadID
+       resp, err := bucket.do("GET", imur.Key, params, options, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       if err != nil {
+               return out, err
+       }
+       err = decodeListUploadedPartsResult(&out)
+       return out, err
+}
+
+// ListMultipartUploads lists all ongoing multipart upload tasks
+//
+// options    listObject's filter. Prefix specifies the returned object's prefix; KeyMarker specifies the returned object's start point in lexicographic order;
+//            MaxKeys specifies the max entries to return; Delimiter is the character for grouping object keys.
+//
+// ListMultipartUploadResponse    the return value if it succeeds, only valid when error is nil.
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) ListMultipartUploads(options ...Option) (ListMultipartUploadResult, error) {
+       var out ListMultipartUploadResult
+
+       options = append(options, EncodingType("url"))
+       params, err := GetRawParams(options)
+       if err != nil {
+               return out, err
+       }
+       params["uploads"] = nil
+
+       resp, err := bucket.do("GET", "", params, options, nil, nil)
+       if err != nil {
+               return out, err
+       }
+       defer resp.Body.Close()
+
+       err = xmlUnmarshal(resp.Body, &out)
+       if err != nil {
+               return out, err
+       }
+       err = decodeListMultipartUploadResult(&out)
+       return out, err
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart_test.go
new file mode 100644 (file)
index 0000000..5727859
--- /dev/null
@@ -0,0 +1,986 @@
+// multipart test
+
+package oss
+
+import (
+       "math/rand"
+       "net/http"
+       "os"
+       "strconv"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssBucketMultipartSuite struct {
+       client *Client
+       bucket *Bucket
+}
+
+var _ = Suite(&OssBucketMultipartSuite{})
+
+// SetUpSuite runs once when the suite starts running
+func (s *OssBucketMultipartSuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       s.client = client
+
+       s.client.CreateBucket(bucketName)
+
+       bucket, err := s.client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       s.bucket = bucket
+
+       // Delete part
+       keyMarker := KeyMarker("")
+       uploadIDMarker := UploadIDMarker("")
+       for {
+               lmur, err := s.bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+               c.Assert(err, IsNil)
+               for _, upload := range lmur.Uploads {
+                       var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName,
+                               Key: upload.Key, UploadID: upload.UploadID}
+                       err = s.bucket.AbortMultipartUpload(imur)
+                       c.Assert(err, IsNil)
+               }
+               keyMarker = KeyMarker(lmur.NextKeyMarker)
+               uploadIDMarker = UploadIDMarker(lmur.NextUploadIDMarker)
+               if !lmur.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete objects
+       marker := Marker("")
+       for {
+               lor, err := s.bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = s.bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       testLogger.Println("test multipart started")
+}
+
+// TearDownSuite runs before each test or benchmark starts running
+func (s *OssBucketMultipartSuite) TearDownSuite(c *C) {
+       // Delete part
+       keyMarker := KeyMarker("")
+       uploadIDMarker := UploadIDMarker("")
+       for {
+               lmur, err := s.bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+               c.Assert(err, IsNil)
+               for _, upload := range lmur.Uploads {
+                       var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName,
+                               Key: upload.Key, UploadID: upload.UploadID}
+                       err = s.bucket.AbortMultipartUpload(imur)
+                       c.Assert(err, IsNil)
+               }
+               keyMarker = KeyMarker(lmur.NextKeyMarker)
+               uploadIDMarker = UploadIDMarker(lmur.NextUploadIDMarker)
+               if !lmur.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete objects
+       marker := Marker("")
+       for {
+               lor, err := s.bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = s.bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete bucket
+       err := s.client.DeleteBucket(s.bucket.BucketName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("test multipart completed")
+}
+
+// SetUpTest runs after each test or benchmark runs
+func (s *OssBucketMultipartSuite) SetUpTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+}
+
+// TearDownTest runs once after all tests or benchmarks have finished running
+func (s *OssBucketMultipartSuite) TearDownTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".temp")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".txt1")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".txt2")
+       c.Assert(err, IsNil)
+}
+
+// TestMultipartUpload
+func (s *OssBucketMultipartSuite) TestMultipartUpload(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       chunks, err := SplitFileByPartNum(fileName, 3)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       options := []Option{
+               Expires(futureDate), Meta("my", "myprop"),
+       }
+
+       fd, err := os.Open(fileName)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       imur, err := s.bucket.InitiateMultipartUpload(objectName, options...)
+       c.Assert(err, IsNil)
+       var parts []UploadPart
+       for _, chunk := range chunks {
+               fd.Seek(chunk.Offset, os.SEEK_SET)
+               part, err := s.bucket.UploadPart(imur, fd, chunk.Size, chunk.Number)
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+       testLogger.Println("cmur:", cmur)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+       err = s.bucket.GetObjectToFile(objectName, "newpic1.jpg")
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestMultipartUploadFromFile
+func (s *OssBucketMultipartSuite) TestMultipartUploadFromFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       chunks, err := SplitFileByPartNum(fileName, 3)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       options := []Option{
+               Expires(futureDate), Meta("my", "myprop"),
+       }
+       imur, err := s.bucket.InitiateMultipartUpload(objectName, options...)
+       c.Assert(err, IsNil)
+       var parts []UploadPart
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, chunk.Number)
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+       testLogger.Println("cmur:", cmur)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+       err = s.bucket.GetObjectToFile(objectName, "newpic1.jpg")
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestUploadPartCopy
+func (s *OssBucketMultipartSuite) TestUploadPartCopy(c *C) {
+       objectSrc := objectNamePrefix + RandStr(8) + "-src"
+       objectDest := objectNamePrefix + RandStr(8) + "-dest"
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       chunks, err := SplitFileByPartNum(fileName, 3)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       err = s.bucket.PutObjectFromFile(objectSrc, fileName)
+       c.Assert(err, IsNil)
+
+       options := []Option{
+               Expires(futureDate), Meta("my", "myprop"),
+       }
+       imur, err := s.bucket.InitiateMultipartUpload(objectDest, options...)
+       c.Assert(err, IsNil)
+       var parts []UploadPart
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+       testLogger.Println("cmur:", cmur)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectDest)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+       c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+       c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+       c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+       err = s.bucket.GetObjectToFile(objectDest, "newpic2.jpg")
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectSrc)
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObject(objectDest)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssBucketMultipartSuite) TestListUploadedParts(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectSrc := objectName + "-src"
+       objectDest := objectName + "-dest"
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       chunks, err := SplitFileByPartSize(fileName, 100*1024)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       err = s.bucket.PutObjectFromFile(objectSrc, fileName)
+       c.Assert(err, IsNil)
+
+       // Upload
+       imurUpload, err := s.bucket.InitiateMultipartUpload(objectName)
+       var partsUpload []UploadPart
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+               partsUpload = append(partsUpload, part)
+       }
+
+       // Copy
+       imurCopy, err := s.bucket.InitiateMultipartUpload(objectDest)
+       var partsCopy []UploadPart
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartCopy(imurCopy, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+               partsCopy = append(partsCopy, part)
+       }
+
+       // List
+       lupr, err := s.bucket.ListUploadedParts(imurUpload)
+       c.Assert(err, IsNil)
+       testLogger.Println("lupr:", lupr)
+       c.Assert(len(lupr.UploadedParts), Equals, len(chunks))
+
+       lupr, err = s.bucket.ListUploadedParts(imurCopy)
+       c.Assert(err, IsNil)
+       testLogger.Println("lupr:", lupr)
+       c.Assert(len(lupr.UploadedParts), Equals, len(chunks))
+
+       lmur, err := s.bucket.ListMultipartUploads()
+       c.Assert(err, IsNil)
+       testLogger.Println("lmur:", lmur)
+
+       // Complete
+       _, err = s.bucket.CompleteMultipartUpload(imurUpload, partsUpload)
+       c.Assert(err, IsNil)
+       _, err = s.bucket.CompleteMultipartUpload(imurCopy, partsCopy)
+       c.Assert(err, IsNil)
+
+       // Download
+       err = s.bucket.GetObjectToFile(objectDest, "newpic3.jpg")
+       c.Assert(err, IsNil)
+       err = s.bucket.GetObjectToFile(objectName, "newpic4.jpg")
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObject(objectDest)
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObject(objectSrc)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssBucketMultipartSuite) TestAbortMultipartUpload(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectSrc := objectName + "-src"
+       objectDest := objectName + "-dest"
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       chunks, err := SplitFileByPartSize(fileName, 100*1024)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       err = s.bucket.PutObjectFromFile(objectSrc, fileName)
+       c.Assert(err, IsNil)
+
+       // Upload
+       imurUpload, err := s.bucket.InitiateMultipartUpload(objectName)
+       var partsUpload []UploadPart
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+               partsUpload = append(partsUpload, part)
+       }
+
+       // Copy
+       imurCopy, err := s.bucket.InitiateMultipartUpload(objectDest)
+       var partsCopy []UploadPart
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartCopy(imurCopy, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+               partsCopy = append(partsCopy, part)
+       }
+
+       // List
+       lupr, err := s.bucket.ListUploadedParts(imurUpload)
+       c.Assert(err, IsNil)
+       testLogger.Println("lupr:", lupr)
+       c.Assert(len(lupr.UploadedParts), Equals, len(chunks))
+
+       lupr, err = s.bucket.ListUploadedParts(imurCopy)
+       c.Assert(err, IsNil)
+       testLogger.Println("lupr:", lupr)
+       c.Assert(len(lupr.UploadedParts), Equals, len(chunks))
+
+       lmur, err := s.bucket.ListMultipartUploads()
+       c.Assert(err, IsNil)
+       testLogger.Println("lmur:", lmur)
+       c.Assert(len(lmur.Uploads), Equals, 2)
+
+       // Abort
+       err = s.bucket.AbortMultipartUpload(imurUpload)
+       c.Assert(err, IsNil)
+       err = s.bucket.AbortMultipartUpload(imurCopy)
+       c.Assert(err, IsNil)
+
+       lmur, err = s.bucket.ListMultipartUploads()
+       c.Assert(err, IsNil)
+       testLogger.Println("lmur:", lmur)
+       c.Assert(len(lmur.Uploads), Equals, 0)
+
+       // Download
+       err = s.bucket.GetObjectToFile(objectDest, "newpic3.jpg")
+       c.Assert(err, NotNil)
+       err = s.bucket.GetObjectToFile(objectName, "newpic4.jpg")
+       c.Assert(err, NotNil)
+}
+
+// TestUploadPartCopyWithConstraints
+func (s *OssBucketMultipartSuite) TestUploadPartCopyWithConstraints(c *C) {
+       objectSrc := objectNamePrefix + RandStr(8) + "-src"
+       objectDest := objectNamePrefix + RandStr(8) + "-dest"
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       chunks, err := SplitFileByPartNum(fileName, 3)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       err = s.bucket.PutObjectFromFile(objectSrc, fileName)
+       c.Assert(err, IsNil)
+
+       imur, err := s.bucket.InitiateMultipartUpload(objectDest)
+       var parts []UploadPart
+       for _, chunk := range chunks {
+               _, err = s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+                       CopySourceIfModifiedSince(futureDate))
+               c.Assert(err, NotNil)
+       }
+
+       for _, chunk := range chunks {
+               _, err = s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+                       CopySourceIfUnmodifiedSince(futureDate))
+               c.Assert(err, IsNil)
+       }
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectSrc)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+
+       for _, chunk := range chunks {
+               _, err = s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+                       CopySourceIfNoneMatch(meta.Get("Etag")))
+               c.Assert(err, NotNil)
+       }
+
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+                       CopySourceIfMatch(meta.Get("Etag")))
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+       testLogger.Println("cmur:", cmur)
+
+       err = s.bucket.GetObjectToFile(objectDest, "newpic5.jpg")
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectSrc)
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObject(objectDest)
+       c.Assert(err, IsNil)
+}
+
+// TestMultipartUploadFromFileOutofOrder
+func (s *OssBucketMultipartSuite) TestMultipartUploadFromFileOutofOrder(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       chunks, err := SplitFileByPartSize(fileName, 1024*100)
+       shuffleArray(chunks)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       imur, err := s.bucket.InitiateMultipartUpload(objectName)
+       var parts []UploadPart
+       for _, chunk := range chunks {
+               _, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+       }
+       // Double upload
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+       testLogger.Println("cmur:", cmur)
+
+       err = s.bucket.GetObjectToFile(objectName, "newpic6.jpg")
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestUploadPartCopyOutofOrder
+func (s *OssBucketMultipartSuite) TestUploadPartCopyOutofOrder(c *C) {
+       objectSrc := objectNamePrefix + RandStr(8) + "-src"
+       objectDest := objectNamePrefix + RandStr(8) + "-dest"
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       chunks, err := SplitFileByPartSize(fileName, 1024*100)
+       shuffleArray(chunks)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       err = s.bucket.PutObjectFromFile(objectSrc, fileName)
+       c.Assert(err, IsNil)
+
+       imur, err := s.bucket.InitiateMultipartUpload(objectDest)
+       var parts []UploadPart
+       for _, chunk := range chunks {
+               _, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+       }
+       // Double copy
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+       testLogger.Println("cmur:", cmur)
+
+       err = s.bucket.GetObjectToFile(objectDest, "newpic7.jpg")
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(objectSrc)
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObject(objectDest)
+       c.Assert(err, IsNil)
+}
+
+// TestMultipartUploadFromFileType
+func (s *OssBucketMultipartSuite) TestMultipartUploadFromFileType(c *C) {
+       objectName := objectNamePrefix + RandStr(8) + ".jpg"
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       chunks, err := SplitFileByPartNum(fileName, 4)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       imur, err := s.bucket.InitiateMultipartUpload(objectName)
+       var parts []UploadPart
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, chunk.Number)
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       testLogger.Println("parts:", parts)
+       cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+       testLogger.Println("cmur:", cmur)
+
+       err = s.bucket.GetObjectToFile(objectName, "newpic8.jpg")
+       c.Assert(err, IsNil)
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("Content-Type"), Equals, "image/jpeg")
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssBucketMultipartSuite) TestListMultipartUploads(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       imurs := []InitiateMultipartUploadResult{}
+       for i := 0; i < 20; i++ {
+               imur, err := s.bucket.InitiateMultipartUpload(objectName + strconv.Itoa(i))
+               c.Assert(err, IsNil)
+               imurs = append(imurs, imur)
+       }
+
+       lmpu, err := s.bucket.ListMultipartUploads()
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 20)
+
+       lmpu, err = s.bucket.ListMultipartUploads(MaxUploads(3))
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 3)
+
+       lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName))
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 20)
+
+       lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName + "1"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 11)
+
+       lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName + "22"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 0)
+
+       lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(objectName + "10"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 17)
+
+       lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(objectName+"10"), MaxUploads(3))
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 3)
+
+       lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName), Delimiter("4"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 18)
+       c.Assert(len(lmpu.CommonPrefixes), Equals, 2)
+
+       upLoadIDStr := RandStr(3)
+       lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(objectName+"12"), UploadIDMarker(upLoadIDStr))
+       c.Assert(err, IsNil)
+       checkNum := 15
+       for _, im := range imurs {
+               if im.Key == objectName+"12" && im.UploadID > upLoadIDStr {
+                       checkNum = 16
+                       break
+               }
+       }
+       c.Assert(len(lmpu.Uploads), Equals, checkNum)
+       //testLogger.Println("UploadIDMarker", lmpu.Uploads)
+
+       for _, imur := range imurs {
+               err = s.bucket.AbortMultipartUpload(imur)
+               c.Assert(err, IsNil)
+       }
+}
+
+func (s *OssBucketMultipartSuite) TestListMultipartUploadsEncodingKey(c *C) {
+       prefix := objectNamePrefix + "让你任性让你狂" + RandStr(8)
+
+       imurs := []InitiateMultipartUploadResult{}
+       for i := 0; i < 3; i++ {
+               imur, err := s.bucket.InitiateMultipartUpload(prefix + strconv.Itoa(i))
+               c.Assert(err, IsNil)
+               imurs = append(imurs, imur)
+       }
+
+       lmpu, err := s.bucket.ListMultipartUploads()
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 3)
+
+       lmpu, err = s.bucket.ListMultipartUploads(Prefix(prefix + "1"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 1)
+
+       lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(prefix + "1"))
+       c.Assert(err, IsNil)
+       c.Assert(len(lmpu.Uploads), Equals, 1)
+
+       lmpu, err = s.bucket.ListMultipartUploads(EncodingType("url"))
+       c.Assert(err, IsNil)
+       for i, upload := range lmpu.Uploads {
+               c.Assert(upload.Key, Equals, prefix+strconv.Itoa(i))
+       }
+
+       for _, imur := range imurs {
+               err = s.bucket.AbortMultipartUpload(imur)
+               c.Assert(err, IsNil)
+       }
+}
+
+func (s *OssBucketMultipartSuite) TestMultipartNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+
+       // Key tool long
+       data := make([]byte, 100*1024)
+       imur, err := s.bucket.InitiateMultipartUpload(string(data))
+       c.Assert(err, NotNil)
+
+       // Invalid imur
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       fd, err := os.Open(fileName)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       _, err = s.bucket.UploadPart(imur, fd, 1024, 1)
+       c.Assert(err, NotNil)
+
+       _, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 1024, 1)
+       c.Assert(err, NotNil)
+
+       _, err = s.bucket.UploadPartCopy(imur, bucketName, fileName, 0, 1024, 1)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.AbortMultipartUpload(imur)
+       c.Assert(err, NotNil)
+
+       _, err = s.bucket.ListUploadedParts(imur)
+       c.Assert(err, NotNil)
+
+       // Invalid exist
+       imur, err = s.bucket.InitiateMultipartUpload(objectName)
+       c.Assert(err, IsNil)
+
+       _, err = s.bucket.UploadPart(imur, fd, 1024, 1)
+       c.Assert(err, IsNil)
+
+       _, err = s.bucket.UploadPart(imur, fd, 102400, 10001)
+       c.Assert(err, NotNil)
+
+       //    _, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 1024, 1)
+       //    c.Assert(err, IsNil)
+
+       _, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 102400, 10001)
+       c.Assert(err, NotNil)
+
+       _, err = s.bucket.UploadPartCopy(imur, bucketName, fileName, 0, 1024, 1)
+       c.Assert(err, NotNil)
+
+       _, err = s.bucket.UploadPartCopy(imur, bucketName, fileName, 0, 1024, 1000)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.AbortMultipartUpload(imur)
+       c.Assert(err, IsNil)
+
+       // Invalid option
+       _, err = s.bucket.InitiateMultipartUpload(objectName, IfModifiedSince(futureDate))
+       c.Assert(err, IsNil)
+}
+
+func (s *OssBucketMultipartSuite) TestMultipartUploadFromFileBigFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       bigFile := "D:\\tmp\\bigfile.zip"
+       newFile := "D:\\tmp\\newbigfile.zip"
+
+       exist, err := isFileExist(bigFile)
+       c.Assert(err, IsNil)
+       if !exist {
+               return
+       }
+
+       chunks, err := SplitFileByPartNum(bigFile, 64)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       imur, err := s.bucket.InitiateMultipartUpload(objectName)
+       var parts []UploadPart
+       start := GetNowSec()
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartFromFile(imur, bigFile, chunk.Offset, chunk.Size, (int)(chunk.Number))
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+       end := GetNowSec()
+       testLogger.Println("Uplaod big file:", bigFile, "use sec:", end-start)
+
+       testLogger.Println("parts:", parts)
+       _, err = s.bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+
+       start = GetNowSec()
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+       end = GetNowSec()
+       testLogger.Println("Download big file:", bigFile, "use sec:", end-start)
+
+       start = GetNowSec()
+       eq, err := compareFiles(bigFile, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+       end = GetNowSec()
+       testLogger.Println("Compare big file:", bigFile, "use sec:", end-start)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestUploadFile
+func (s *OssBucketMultipartSuite) TestUploadFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       // Upload with 100K part size
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024)
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Upload with part size equals to 1/4 of the file size
+       err = s.bucket.UploadFile(objectName, fileName, 482048/4)
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Upload with part size equals to the file size
+       err = s.bucket.UploadFile(objectName, fileName, 482048)
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Upload with part size is bigger than the file size
+       err = s.bucket.UploadFile(objectName, fileName, 482049)
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Option
+       options := []Option{
+               Expires(futureDate),
+               ObjectACL(ACLPublicRead),
+               Meta("myprop", "mypropval")}
+       err = s.bucket.UploadFile(objectName, fileName, 482049, options...)
+       c.Assert(err, IsNil)
+
+       // Check
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       acl, err := s.bucket.GetObjectACL(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectAcl:", acl)
+       c.Assert(acl.ACL, Equals, "public-read")
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+}
+
+func (s *OssBucketMultipartSuite) TestUploadFileNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       // Smaller than the required minimal part size (100KB)
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024-1)
+       c.Assert(err, NotNil)
+
+       // Bigger than the max part size (5G)
+       err = s.bucket.UploadFile(objectName, fileName, 1024*1024*1024*5+1)
+       c.Assert(err, NotNil)
+
+       // File does not exist
+       err = s.bucket.UploadFile(objectName, "/root1/123abc9874", 1024*1024*1024)
+       c.Assert(err, NotNil)
+
+       // Invalid key , key is empty.
+       err = s.bucket.UploadFile("", fileName, 100*1024)
+       c.Assert(err, NotNil)
+}
+
+// TestDownloadFile
+func (s *OssBucketMultipartSuite) TestDownloadFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024)
+       c.Assert(err, IsNil)
+
+       // Download file with part size of 100K
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024)
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download the file with part size equals to 1/4 of the file size
+       err = s.bucket.DownloadFile(objectName, newFile, 482048/4)
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download the file with part size same as the file size
+       err = s.bucket.DownloadFile(objectName, newFile, 482048)
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Download the file with part size bigger than the file size
+       err = s.bucket.DownloadFile(objectName, newFile, 482049)
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // Option
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       testLogger.Println("GetObjectDetailedMeta:", meta)
+
+       // If-Match
+       err = s.bucket.DownloadFile(objectName, newFile, 482048/4, IfMatch(meta.Get("Etag")))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       // If-None-Match
+       err = s.bucket.DownloadFile(objectName, newFile, 482048, IfNoneMatch(meta.Get("Etag")))
+       c.Assert(err, NotNil)
+
+       os.Remove(newFile)
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssBucketMultipartSuite) TestDownloadFileNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       newFile := RandStr(8) + ".jpg"
+
+       // Smaller than the required minimal part size (100KB)
+       err := s.bucket.DownloadFile(objectName, newFile, 100*1024-1)
+       c.Assert(err, NotNil)
+
+       // Bigger than the required max part size (5G)
+       err = s.bucket.DownloadFile(objectName, newFile, 1024*1024*1024+1)
+       c.Assert(err, NotNil)
+
+       // File does not exist
+       err = s.bucket.DownloadFile(objectName, "/OSS/TEMP/ZIBI/QUQU/BALA", 1024*1024*1024+1)
+       c.Assert(err, NotNil)
+
+       // Key does not exist
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024)
+       c.Assert(err, NotNil)
+}
+
+// Private
+func shuffleArray(chunks []FileChunk) []FileChunk {
+       for i := range chunks {
+               j := rand.Intn(i + 1)
+               chunks[i], chunks[j] = chunks[j], chunks[i]
+       }
+       return chunks
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option.go
new file mode 100644 (file)
index 0000000..779b00e
--- /dev/null
@@ -0,0 +1,638 @@
+package oss
+
+import (
+       "fmt"
+       "net/http"
+       "net/url"
+       "strconv"
+       "strings"
+       "time"
+)
+
+type optionType string
+
+const (
+       optionParam optionType = "HTTPParameter" // URL parameter
+       optionHTTP  optionType = "HTTPHeader"    // HTTP header
+       optionArg   optionType = "FuncArgument"  // Function argument
+)
+
+const (
+       deleteObjectsQuiet = "delete-objects-quiet"
+       routineNum         = "x-routine-num"
+       checkpointConfig   = "x-cp-config"
+       initCRC64          = "init-crc64"
+       progressListener   = "x-progress-listener"
+       storageClass       = "storage-class"
+       responseHeader     = "x-response-header"
+       redundancyType     = "redundancy-type"
+)
+
+type (
+       optionValue struct {
+               Value interface{}
+               Type  optionType
+       }
+
+       // Option HTTP option
+       Option func(map[string]optionValue) error
+)
+
+// ACL is an option to set X-Oss-Acl header
+func ACL(acl ACLType) Option {
+       return setHeader(HTTPHeaderOssACL, string(acl))
+}
+
+// ContentType is an option to set Content-Type header
+func ContentType(value string) Option {
+       return setHeader(HTTPHeaderContentType, value)
+}
+
+// ContentLength is an option to set Content-Length header
+func ContentLength(length int64) Option {
+       return setHeader(HTTPHeaderContentLength, strconv.FormatInt(length, 10))
+}
+
+// CacheControl is an option to set Cache-Control header
+func CacheControl(value string) Option {
+       return setHeader(HTTPHeaderCacheControl, value)
+}
+
+// ContentDisposition is an option to set Content-Disposition header
+func ContentDisposition(value string) Option {
+       return setHeader(HTTPHeaderContentDisposition, value)
+}
+
+// ContentEncoding is an option to set Content-Encoding header
+func ContentEncoding(value string) Option {
+       return setHeader(HTTPHeaderContentEncoding, value)
+}
+
+// ContentLanguage is an option to set Content-Language header
+func ContentLanguage(value string) Option {
+       return setHeader(HTTPHeaderContentLanguage, value)
+}
+
+// ContentMD5 is an option to set Content-MD5 header
+func ContentMD5(value string) Option {
+       return setHeader(HTTPHeaderContentMD5, value)
+}
+
+// Expires is an option to set Expires header
+func Expires(t time.Time) Option {
+       return setHeader(HTTPHeaderExpires, t.Format(http.TimeFormat))
+}
+
+// Meta is an option to set Meta header
+func Meta(key, value string) Option {
+       return setHeader(HTTPHeaderOssMetaPrefix+key, value)
+}
+
+// Range is an option to set Range header, [start, end]
+func Range(start, end int64) Option {
+       return setHeader(HTTPHeaderRange, fmt.Sprintf("bytes=%d-%d", start, end))
+}
+
+// NormalizedRange is an option to set Range header, such as 1024-2048 or 1024- or -2048
+func NormalizedRange(nr string) Option {
+       return setHeader(HTTPHeaderRange, fmt.Sprintf("bytes=%s", strings.TrimSpace(nr)))
+}
+
+// AcceptEncoding is an option to set Accept-Encoding header
+func AcceptEncoding(value string) Option {
+       return setHeader(HTTPHeaderAcceptEncoding, value)
+}
+
+// IfModifiedSince is an option to set If-Modified-Since header
+func IfModifiedSince(t time.Time) Option {
+       return setHeader(HTTPHeaderIfModifiedSince, t.Format(http.TimeFormat))
+}
+
+// IfUnmodifiedSince is an option to set If-Unmodified-Since header
+func IfUnmodifiedSince(t time.Time) Option {
+       return setHeader(HTTPHeaderIfUnmodifiedSince, t.Format(http.TimeFormat))
+}
+
+// IfMatch is an option to set If-Match header
+func IfMatch(value string) Option {
+       return setHeader(HTTPHeaderIfMatch, value)
+}
+
+// IfNoneMatch is an option to set IfNoneMatch header
+func IfNoneMatch(value string) Option {
+       return setHeader(HTTPHeaderIfNoneMatch, value)
+}
+
+// CopySource is an option to set X-Oss-Copy-Source header
+func CopySource(sourceBucket, sourceObject string) Option {
+       return setHeader(HTTPHeaderOssCopySource, "/"+sourceBucket+"/"+sourceObject)
+}
+
+// CopySourceVersion is an option to set X-Oss-Copy-Source header,include versionId
+func CopySourceVersion(sourceBucket, sourceObject string, versionId string) Option {
+       return setHeader(HTTPHeaderOssCopySource, "/"+sourceBucket+"/"+sourceObject+"?"+"versionId="+versionId)
+}
+
+// CopySourceRange is an option to set X-Oss-Copy-Source header
+func CopySourceRange(startPosition, partSize int64) Option {
+       val := "bytes=" + strconv.FormatInt(startPosition, 10) + "-" +
+               strconv.FormatInt((startPosition+partSize-1), 10)
+       return setHeader(HTTPHeaderOssCopySourceRange, val)
+}
+
+// CopySourceIfMatch is an option to set X-Oss-Copy-Source-If-Match header
+func CopySourceIfMatch(value string) Option {
+       return setHeader(HTTPHeaderOssCopySourceIfMatch, value)
+}
+
+// CopySourceIfNoneMatch is an option to set X-Oss-Copy-Source-If-None-Match header
+func CopySourceIfNoneMatch(value string) Option {
+       return setHeader(HTTPHeaderOssCopySourceIfNoneMatch, value)
+}
+
+// CopySourceIfModifiedSince is an option to set X-Oss-CopySource-If-Modified-Since header
+func CopySourceIfModifiedSince(t time.Time) Option {
+       return setHeader(HTTPHeaderOssCopySourceIfModifiedSince, t.Format(http.TimeFormat))
+}
+
+// CopySourceIfUnmodifiedSince is an option to set X-Oss-Copy-Source-If-Unmodified-Since header
+func CopySourceIfUnmodifiedSince(t time.Time) Option {
+       return setHeader(HTTPHeaderOssCopySourceIfUnmodifiedSince, t.Format(http.TimeFormat))
+}
+
+// MetadataDirective is an option to set X-Oss-Metadata-Directive header
+func MetadataDirective(directive MetadataDirectiveType) Option {
+       return setHeader(HTTPHeaderOssMetadataDirective, string(directive))
+}
+
+// ServerSideEncryption is an option to set X-Oss-Server-Side-Encryption header
+func ServerSideEncryption(value string) Option {
+       return setHeader(HTTPHeaderOssServerSideEncryption, value)
+}
+
+// ServerSideEncryptionKeyID is an option to set X-Oss-Server-Side-Encryption-Key-Id header
+func ServerSideEncryptionKeyID(value string) Option {
+       return setHeader(HTTPHeaderOssServerSideEncryptionKeyID, value)
+}
+
+// ServerSideDataEncryption is an option to set X-Oss-Server-Side-Data-Encryption header
+func ServerSideDataEncryption(value string) Option {
+       return setHeader(HTTPHeaderOssServerSideDataEncryption, value)
+}
+
+// SSECAlgorithm is an option to set X-Oss-Server-Side-Encryption-Customer-Algorithm header
+func SSECAlgorithm(value string) Option {
+       return setHeader(HTTPHeaderSSECAlgorithm, value)
+}
+
+// SSECKey is an option to set X-Oss-Server-Side-Encryption-Customer-Key header
+func SSECKey(value string) Option {
+       return setHeader(HTTPHeaderSSECKey, value)
+}
+
+// SSECKeyMd5 is an option to set X-Oss-Server-Side-Encryption-Customer-Key-Md5 header
+func SSECKeyMd5(value string) Option {
+       return setHeader(HTTPHeaderSSECKeyMd5, value)
+}
+
+// ObjectACL is an option to set X-Oss-Object-Acl header
+func ObjectACL(acl ACLType) Option {
+       return setHeader(HTTPHeaderOssObjectACL, string(acl))
+}
+
+// symlinkTarget is an option to set X-Oss-Symlink-Target
+func symlinkTarget(targetObjectKey string) Option {
+       return setHeader(HTTPHeaderOssSymlinkTarget, targetObjectKey)
+}
+
+// Origin is an option to set Origin header
+func Origin(value string) Option {
+       return setHeader(HTTPHeaderOrigin, value)
+}
+
+// ObjectStorageClass is an option to set the storage class of object
+func ObjectStorageClass(storageClass StorageClassType) Option {
+       return setHeader(HTTPHeaderOssStorageClass, string(storageClass))
+}
+
+// Callback is an option to set callback values
+func Callback(callback string) Option {
+       return setHeader(HTTPHeaderOssCallback, callback)
+}
+
+// CallbackVar is an option to set callback user defined values
+func CallbackVar(callbackVar string) Option {
+       return setHeader(HTTPHeaderOssCallbackVar, callbackVar)
+}
+
+// RequestPayer is an option to set payer who pay for the request
+func RequestPayer(payerType PayerType) Option {
+       return setHeader(HTTPHeaderOssRequester, strings.ToLower(string(payerType)))
+}
+
+// RequestPayerParam is an option to set payer who pay for the request
+func RequestPayerParam(payerType PayerType) Option {
+       return addParam(strings.ToLower(HTTPHeaderOssRequester), strings.ToLower(string(payerType)))
+}
+
+// SetTagging is an option to set object tagging
+func SetTagging(tagging Tagging) Option {
+       if len(tagging.Tags) == 0 {
+               return nil
+       }
+
+       taggingValue := ""
+       for index, tag := range tagging.Tags {
+               if index != 0 {
+                       taggingValue += "&"
+               }
+               taggingValue += url.QueryEscape(tag.Key) + "=" + url.QueryEscape(tag.Value)
+       }
+       return setHeader(HTTPHeaderOssTagging, taggingValue)
+}
+
+// TaggingDirective is an option to set X-Oss-Metadata-Directive header
+func TaggingDirective(directive TaggingDirectiveType) Option {
+       return setHeader(HTTPHeaderOssTaggingDirective, string(directive))
+}
+
+// ACReqMethod is an option to set Access-Control-Request-Method header
+func ACReqMethod(value string) Option {
+       return setHeader(HTTPHeaderACReqMethod, value)
+}
+
+// ACReqHeaders is an option to set Access-Control-Request-Headers header
+func ACReqHeaders(value string) Option {
+       return setHeader(HTTPHeaderACReqHeaders, value)
+}
+
+// TrafficLimitHeader is an option to set X-Oss-Traffic-Limit
+func TrafficLimitHeader(value int64) Option {
+       return setHeader(HTTPHeaderOssTrafficLimit, strconv.FormatInt(value, 10))
+}
+
+// UserAgentHeader is an option to set HTTPHeaderUserAgent
+func UserAgentHeader(ua string) Option {
+       return setHeader(HTTPHeaderUserAgent, ua)
+}
+
+// ForbidOverWrite  is an option to set X-Oss-Forbid-Overwrite
+func ForbidOverWrite(forbidWrite bool) Option {
+       if forbidWrite {
+               return setHeader(HTTPHeaderOssForbidOverWrite, "true")
+       } else {
+               return setHeader(HTTPHeaderOssForbidOverWrite, "false")
+       }
+}
+
+// RangeBehavior  is an option to set Range value, such as "standard"
+func RangeBehavior(value string) Option {
+       return setHeader(HTTPHeaderOssRangeBehavior, value)
+}
+
+// Delimiter is an option to set delimiler parameter
+func Delimiter(value string) Option {
+       return addParam("delimiter", value)
+}
+
+// Marker is an option to set marker parameter
+func Marker(value string) Option {
+       return addParam("marker", value)
+}
+
+// MaxKeys is an option to set maxkeys parameter
+func MaxKeys(value int) Option {
+       return addParam("max-keys", strconv.Itoa(value))
+}
+
+// Prefix is an option to set prefix parameter
+func Prefix(value string) Option {
+       return addParam("prefix", value)
+}
+
+// EncodingType is an option to set encoding-type parameter
+func EncodingType(value string) Option {
+       return addParam("encoding-type", value)
+}
+
+// MaxUploads is an option to set max-uploads parameter
+func MaxUploads(value int) Option {
+       return addParam("max-uploads", strconv.Itoa(value))
+}
+
+// KeyMarker is an option to set key-marker parameter
+func KeyMarker(value string) Option {
+       return addParam("key-marker", value)
+}
+
+// VersionIdMarker is an option to set version-id-marker parameter
+func VersionIdMarker(value string) Option {
+       return addParam("version-id-marker", value)
+}
+
+// VersionId is an option to set versionId parameter
+func VersionId(value string) Option {
+       return addParam("versionId", value)
+}
+
+// TagKey is an option to set tag key parameter
+func TagKey(value string) Option {
+       return addParam("tag-key", value)
+}
+
+// TagValue is an option to set tag value parameter
+func TagValue(value string) Option {
+       return addParam("tag-value", value)
+}
+
+// UploadIDMarker is an option to set upload-id-marker parameter
+func UploadIDMarker(value string) Option {
+       return addParam("upload-id-marker", value)
+}
+
+// MaxParts is an option to set max-parts parameter
+func MaxParts(value int) Option {
+       return addParam("max-parts", strconv.Itoa(value))
+}
+
+// PartNumberMarker is an option to set part-number-marker parameter
+func PartNumberMarker(value int) Option {
+       return addParam("part-number-marker", strconv.Itoa(value))
+}
+
+// Sequential is an option to set sequential parameter for InitiateMultipartUpload
+func Sequential() Option {
+       return addParam("sequential", "")
+}
+
+// ListType is an option to set List-type parameter for ListObjectsV2
+func ListType(value int) Option {
+       return addParam("list-type", strconv.Itoa(value))
+}
+
+// StartAfter is an option to set start-after parameter for ListObjectsV2
+func StartAfter(value string) Option {
+       return addParam("start-after", value)
+}
+
+// ContinuationToken is an option to set Continuation-token parameter for ListObjectsV2
+func ContinuationToken(value string) Option {
+       if value == "" {
+               return addParam("continuation-token", nil)
+       }
+       return addParam("continuation-token", value)
+}
+
+// FetchOwner is an option to set Fetch-owner parameter for ListObjectsV2
+func FetchOwner(value bool) Option {
+       if value {
+               return addParam("fetch-owner", "true")
+       }
+       return addParam("fetch-owner", "false")
+}
+
+// DeleteObjectsQuiet false:DeleteObjects in verbose mode; true:DeleteObjects in quite mode. Default is false.
+func DeleteObjectsQuiet(isQuiet bool) Option {
+       return addArg(deleteObjectsQuiet, isQuiet)
+}
+
+// StorageClass bucket storage class
+func StorageClass(value StorageClassType) Option {
+       return addArg(storageClass, value)
+}
+
+// RedundancyType bucket data redundancy type
+func RedundancyType(value DataRedundancyType) Option {
+       return addArg(redundancyType, value)
+}
+
+// Checkpoint configuration
+type cpConfig struct {
+       IsEnable bool
+       FilePath string
+       DirPath  string
+}
+
+// Checkpoint sets the isEnable flag and checkpoint file path for DownloadFile/UploadFile.
+func Checkpoint(isEnable bool, filePath string) Option {
+       return addArg(checkpointConfig, &cpConfig{IsEnable: isEnable, FilePath: filePath})
+}
+
+// CheckpointDir sets the isEnable flag and checkpoint dir path for DownloadFile/UploadFile.
+func CheckpointDir(isEnable bool, dirPath string) Option {
+       return addArg(checkpointConfig, &cpConfig{IsEnable: isEnable, DirPath: dirPath})
+}
+
+// Routines DownloadFile/UploadFile routine count
+func Routines(n int) Option {
+       return addArg(routineNum, n)
+}
+
+// InitCRC Init AppendObject CRC
+func InitCRC(initCRC uint64) Option {
+       return addArg(initCRC64, initCRC)
+}
+
+// Progress set progress listener
+func Progress(listener ProgressListener) Option {
+       return addArg(progressListener, listener)
+}
+
+// GetResponseHeader for get response http header
+func GetResponseHeader(respHeader *http.Header) Option {
+       return addArg(responseHeader, respHeader)
+}
+
+// ResponseContentType is an option to set response-content-type param
+func ResponseContentType(value string) Option {
+       return addParam("response-content-type", value)
+}
+
+// ResponseContentLanguage is an option to set response-content-language param
+func ResponseContentLanguage(value string) Option {
+       return addParam("response-content-language", value)
+}
+
+// ResponseExpires is an option to set response-expires param
+func ResponseExpires(value string) Option {
+       return addParam("response-expires", value)
+}
+
+// ResponseCacheControl is an option to set response-cache-control param
+func ResponseCacheControl(value string) Option {
+       return addParam("response-cache-control", value)
+}
+
+// ResponseContentDisposition is an option to set response-content-disposition param
+func ResponseContentDisposition(value string) Option {
+       return addParam("response-content-disposition", value)
+}
+
+// ResponseContentEncoding is an option to set response-content-encoding param
+func ResponseContentEncoding(value string) Option {
+       return addParam("response-content-encoding", value)
+}
+
+// Process is an option to set x-oss-process param
+func Process(value string) Option {
+       return addParam("x-oss-process", value)
+}
+
+// TrafficLimitParam is a option to set x-oss-traffic-limit
+func TrafficLimitParam(value int64) Option {
+       return addParam("x-oss-traffic-limit", strconv.FormatInt(value, 10))
+}
+
+// SetHeader Allow users to set personalized http headers
+func SetHeader(key string, value interface{}) Option {
+       return setHeader(key, value)
+}
+
+// AddParam Allow users to set personalized http params
+func AddParam(key string, value interface{}) Option {
+       return addParam(key, value)
+}
+
+func setHeader(key string, value interface{}) Option {
+       return func(params map[string]optionValue) error {
+               if value == nil {
+                       return nil
+               }
+               params[key] = optionValue{value, optionHTTP}
+               return nil
+       }
+}
+
+func addParam(key string, value interface{}) Option {
+       return func(params map[string]optionValue) error {
+               if value == nil {
+                       return nil
+               }
+               params[key] = optionValue{value, optionParam}
+               return nil
+       }
+}
+
+func addArg(key string, value interface{}) Option {
+       return func(params map[string]optionValue) error {
+               if value == nil {
+                       return nil
+               }
+               params[key] = optionValue{value, optionArg}
+               return nil
+       }
+}
+
+func handleOptions(headers map[string]string, options []Option) error {
+       params := map[string]optionValue{}
+       for _, option := range options {
+               if option != nil {
+                       if err := option(params); err != nil {
+                               return err
+                       }
+               }
+       }
+
+       for k, v := range params {
+               if v.Type == optionHTTP {
+                       headers[k] = v.Value.(string)
+               }
+       }
+       return nil
+}
+
+func GetRawParams(options []Option) (map[string]interface{}, error) {
+       // Option
+       params := map[string]optionValue{}
+       for _, option := range options {
+               if option != nil {
+                       if err := option(params); err != nil {
+                               return nil, err
+                       }
+               }
+       }
+
+       paramsm := map[string]interface{}{}
+       // Serialize
+       for k, v := range params {
+               if v.Type == optionParam {
+                       vs := params[k]
+                       paramsm[k] = vs.Value.(string)
+               }
+       }
+
+       return paramsm, nil
+}
+
+func FindOption(options []Option, param string, defaultVal interface{}) (interface{}, error) {
+       params := map[string]optionValue{}
+       for _, option := range options {
+               if option != nil {
+                       if err := option(params); err != nil {
+                               return nil, err
+                       }
+               }
+       }
+
+       if val, ok := params[param]; ok {
+               return val.Value, nil
+       }
+       return defaultVal, nil
+}
+
+func IsOptionSet(options []Option, option string) (bool, interface{}, error) {
+       params := map[string]optionValue{}
+       for _, option := range options {
+               if option != nil {
+                       if err := option(params); err != nil {
+                               return false, nil, err
+                       }
+               }
+       }
+
+       if val, ok := params[option]; ok {
+               return true, val.Value, nil
+       }
+       return false, nil, nil
+}
+
+func DeleteOption(options []Option, strKey string) []Option {
+       var outOption []Option
+       params := map[string]optionValue{}
+       for _, option := range options {
+               if option != nil {
+                       option(params)
+                       _, exist := params[strKey]
+                       if !exist {
+                               outOption = append(outOption, option)
+                       } else {
+                               delete(params, strKey)
+                       }
+               }
+       }
+       return outOption
+}
+
+func GetRequestId(header http.Header) string {
+       return header.Get("x-oss-request-id")
+}
+
+func GetVersionId(header http.Header) string {
+       return header.Get("x-oss-version-id")
+}
+
+func GetCopySrcVersionId(header http.Header) string {
+       return header.Get("x-oss-copy-source-version-id")
+}
+
+func GetDeleteMark(header http.Header) bool {
+       value := header.Get("x-oss-delete-marker")
+       if strings.ToUpper(value) == "TRUE" {
+               return true
+       }
+       return false
+}
+
+func GetQosDelayTime(header http.Header) string {
+       return header.Get("x-oss-qos-delay-time")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option_test.go
new file mode 100644 (file)
index 0000000..f5c7e7d
--- /dev/null
@@ -0,0 +1,317 @@
+package oss
+
+import (
+       "net/http"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssOptionSuite struct{}
+
+var _ = Suite(&OssOptionSuite{})
+
+type optionTestCase struct {
+       option Option
+       key    string
+       value  string
+}
+
+var headerTestcases = []optionTestCase{
+       {
+               option: Meta("User", "baymax"),
+               key:    "X-Oss-Meta-User",
+               value:  "baymax",
+       },
+       {
+               option: ACL(ACLPrivate),
+               key:    "X-Oss-Acl",
+               value:  "private",
+       },
+       {
+               option: ContentType("plain/text"),
+               key:    "Content-Type",
+               value:  "plain/text",
+       },
+       {
+               option: CacheControl("no-cache"),
+               key:    "Cache-Control",
+               value:  "no-cache",
+       },
+       {
+               option: ContentDisposition("Attachment; filename=example.txt"),
+               key:    "Content-Disposition",
+               value:  "Attachment; filename=example.txt",
+       },
+       {
+               option: ContentEncoding("gzip"),
+               key:    "Content-Encoding",
+               value:  "gzip",
+       },
+       {
+               option: Expires(pastDate),
+               key:    "Expires",
+               value:  pastDate.Format(http.TimeFormat),
+       },
+       {
+               option: Range(0, 9),
+               key:    "Range",
+               value:  "bytes=0-9",
+       },
+       {
+               option: Origin("localhost"),
+               key:    "Origin",
+               value:  "localhost",
+       },
+       {
+               option: CopySourceRange(0, 9),
+               key:    "X-Oss-Copy-Source-Range",
+               value:  "bytes=0-8",
+       },
+       {
+               option: IfModifiedSince(pastDate),
+               key:    "If-Modified-Since",
+               value:  pastDate.Format(http.TimeFormat),
+       },
+       {
+               option: IfUnmodifiedSince(futureDate),
+               key:    "If-Unmodified-Since",
+               value:  futureDate.Format(http.TimeFormat),
+       },
+       {
+               option: IfMatch("xyzzy"),
+               key:    "If-Match",
+               value:  "xyzzy",
+       },
+       {
+               option: IfNoneMatch("xyzzy"),
+               key:    "If-None-Match",
+               value:  "xyzzy",
+       },
+       {
+               option: CopySource("bucket_name", "object_name"),
+               key:    "X-Oss-Copy-Source",
+               value:  "/bucket_name/object_name",
+       },
+       {
+               option: CopySourceIfModifiedSince(pastDate),
+               key:    "X-Oss-Copy-Source-If-Modified-Since",
+               value:  pastDate.Format(http.TimeFormat),
+       },
+       {
+               option: CopySourceIfUnmodifiedSince(futureDate),
+               key:    "X-Oss-Copy-Source-If-Unmodified-Since",
+               value:  futureDate.Format(http.TimeFormat),
+       },
+       {
+               option: CopySourceIfMatch("xyzzy"),
+               key:    "X-Oss-Copy-Source-If-Match",
+               value:  "xyzzy",
+       },
+       {
+               option: CopySourceIfNoneMatch("xyzzy"),
+               key:    "X-Oss-Copy-Source-If-None-Match",
+               value:  "xyzzy",
+       },
+       {
+               option: MetadataDirective(MetaCopy),
+               key:    "X-Oss-Metadata-Directive",
+               value:  "COPY",
+       },
+       {
+               option: ServerSideEncryption("AES256"),
+               key:    "X-Oss-Server-Side-Encryption",
+               value:  "AES256",
+       },
+       {
+               option: ObjectACL(ACLPrivate),
+               key:    "X-Oss-Object-Acl",
+               value:  "private",
+       },
+       {
+               option: ObjectStorageClass(StorageStandard),
+               key:    "X-Oss-Storage-Class",
+               value:  "Standard",
+       },
+       {
+               option: Callback("JTdCJTIyY2FsbGJhY2tVcmwlMjIlM0ElMjJleGFtcGxlLmNvbS9pbmRleC5odG1sJTIyJTdE"),
+               key:    "X-Oss-Callback",
+               value:  "JTdCJTIyY2FsbGJhY2tVcmwlMjIlM0ElMjJleGFtcGxlLmNvbS9pbmRleC5odG1sJTIyJTdE",
+       },
+       {
+               option: CallbackVar("JTdCJTIyeCUzQXZhcjElMjIlM0ElMjJ2YWx1ZTElMjIlMkMlMjJ4JTNBdmFyMiUyMiUzQSUyMnZhbHVlMiUyMiU3RA=="),
+               key:    "X-Oss-Callback-Var",
+               value:  "JTdCJTIyeCUzQXZhcjElMjIlM0ElMjJ2YWx1ZTElMjIlMkMlMjJ4JTNBdmFyMiUyMiUzQSUyMnZhbHVlMiUyMiU3RA==",
+       },
+       {
+               option: ContentLanguage("zh-CN"),
+               key:    "Content-Language",
+               value:  "zh-CN",
+       },
+       {
+               option: ServerSideEncryptionKeyID("xossekid"),
+               key:    "X-Oss-Server-Side-Encryption-Key-Id",
+               value:  "xossekid",
+       },
+}
+
+func (s *OssOptionSuite) TestHeaderOptions(c *C) {
+       for _, testcase := range headerTestcases {
+               headers := make(map[string]optionValue)
+               err := testcase.option(headers)
+               c.Assert(err, IsNil)
+
+               expected, actual := testcase.value, headers[testcase.key].Value
+               c.Assert(expected, Equals, actual)
+       }
+}
+
+var paramTestCases = []optionTestCase{
+       {
+               option: Delimiter("/"),
+               key:    "delimiter",
+               value:  "/",
+       },
+       {
+               option: Marker("abc"),
+               key:    "marker",
+               value:  "abc",
+       },
+       {
+               option: MaxKeys(150),
+               key:    "max-keys",
+               value:  "150",
+       },
+       {
+               option: Prefix("fun"),
+               key:    "prefix",
+               value:  "fun",
+       },
+       {
+               option: EncodingType("ascii"),
+               key:    "encoding-type",
+               value:  "ascii",
+       },
+       {
+               option: MaxUploads(100),
+               key:    "max-uploads",
+               value:  "100",
+       },
+       {
+               option: KeyMarker("abc"),
+               key:    "key-marker",
+               value:  "abc",
+       },
+       {
+               option: UploadIDMarker("xyz"),
+               key:    "upload-id-marker",
+               value:  "xyz",
+       },
+       {
+               option: MaxParts(1000),
+               key:    "max-parts",
+               value:  "1000",
+       },
+       {
+               option: PartNumberMarker(1),
+               key:    "part-number-marker",
+               value:  "1",
+       },
+       {
+               option: Process("image/format,png"),
+               key:    "x-oss-process",
+               value:  "image/format,png",
+       },
+}
+
+func (s *OssOptionSuite) TestParamOptions(c *C) {
+       for _, testcase := range paramTestCases {
+               params := make(map[string]optionValue)
+               err := testcase.option(params)
+               c.Assert(err, IsNil)
+
+               expected, actual := testcase.value, params[testcase.key].Value
+               c.Assert(expected, Equals, actual)
+       }
+}
+
+func (s *OssOptionSuite) TestHandleOptions(c *C) {
+       headers := make(map[string]string)
+       options := []Option{}
+
+       for _, testcase := range headerTestcases {
+               options = append(options, testcase.option)
+       }
+
+       err := handleOptions(headers, options)
+       c.Assert(err, IsNil)
+
+       for _, testcase := range headerTestcases {
+               expected, actual := testcase.value, headers[testcase.key]
+               c.Assert(expected, Equals, actual)
+       }
+
+       options = []Option{IfMatch(""), nil}
+       headers = map[string]string{}
+       err = handleOptions(headers, options)
+       c.Assert(err, IsNil)
+       c.Assert(len(headers), Equals, 1)
+}
+
+func (s *OssOptionSuite) TestHandleParams(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       options := []Option{}
+
+       for _, testcase := range paramTestCases {
+               options = append(options, testcase.option)
+       }
+
+       params, err := GetRawParams(options)
+       c.Assert(err, IsNil)
+
+       out := client.Conn.getURLParams(params)
+       c.Assert(len(out), Equals, 191)
+
+       options = []Option{KeyMarker(""), nil}
+
+       params, err = GetRawParams(options)
+       c.Assert(err, IsNil)
+
+       out = client.Conn.getURLParams(params)
+       c.Assert(out, Equals, "key-marker=")
+}
+
+func (s *OssOptionSuite) TestFindOption(c *C) {
+       options := []Option{}
+
+       for _, testcase := range headerTestcases {
+               options = append(options, testcase.option)
+       }
+
+       str, err := FindOption(options, "X-Oss-Acl", "")
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, "private")
+
+       str, err = FindOption(options, "MyProp", "")
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, "")
+}
+
+func (s *OssOptionSuite) TestDeleteOption(c *C) {
+       options := []Option{VersionId("123"), VersionIdMarker("456"), KeyMarker("789")}
+       str, err := FindOption(options, "versionId", "")
+       c.Assert(str, Equals, "123")
+       c.Assert(err, IsNil)
+
+       skipOption := DeleteOption(options, "versionId")
+       str, err = FindOption(skipOption, "versionId", "")
+       c.Assert(str, Equals, "")
+
+       str, err = FindOption(skipOption, "version-id-marker", "")
+       c.Assert(str, Equals, "456")
+
+       str, err = FindOption(skipOption, "key-marker", "")
+       c.Assert(str, Equals, "789")
+
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress.go
new file mode 100644 (file)
index 0000000..33cbebd
--- /dev/null
@@ -0,0 +1,114 @@
+package oss
+
+import "io"
+
+// ProgressEventType defines transfer progress event type
+type ProgressEventType int
+
+const (
+       // TransferStartedEvent transfer started, set TotalBytes
+       TransferStartedEvent ProgressEventType = 1 + iota
+       // TransferDataEvent transfer data, set ConsumedBytes anmd TotalBytes
+       TransferDataEvent
+       // TransferCompletedEvent transfer completed
+       TransferCompletedEvent
+       // TransferFailedEvent transfer encounters an error
+       TransferFailedEvent
+)
+
+// ProgressEvent defines progress event
+type ProgressEvent struct {
+       ConsumedBytes int64
+       TotalBytes    int64
+       RwBytes       int64
+       EventType     ProgressEventType
+}
+
+// ProgressListener listens progress change
+type ProgressListener interface {
+       ProgressChanged(event *ProgressEvent)
+}
+
+// -------------------- Private --------------------
+
+func newProgressEvent(eventType ProgressEventType, consumed, total int64, rwBytes int64) *ProgressEvent {
+       return &ProgressEvent{
+               ConsumedBytes: consumed,
+               TotalBytes:    total,
+               RwBytes:       rwBytes,
+               EventType:     eventType}
+}
+
+// publishProgress
+func publishProgress(listener ProgressListener, event *ProgressEvent) {
+       if listener != nil && event != nil {
+               listener.ProgressChanged(event)
+       }
+}
+
+type readerTracker struct {
+       completedBytes int64
+}
+
+type teeReader struct {
+       reader        io.Reader
+       writer        io.Writer
+       listener      ProgressListener
+       consumedBytes int64
+       totalBytes    int64
+       tracker       *readerTracker
+}
+
+// TeeReader returns a Reader that writes to w what it reads from r.
+// All reads from r performed through it are matched with
+// corresponding writes to w.  There is no internal buffering -
+// the write must complete before the read completes.
+// Any error encountered while writing is reported as a read error.
+func TeeReader(reader io.Reader, writer io.Writer, totalBytes int64, listener ProgressListener, tracker *readerTracker) io.ReadCloser {
+       return &teeReader{
+               reader:        reader,
+               writer:        writer,
+               listener:      listener,
+               consumedBytes: 0,
+               totalBytes:    totalBytes,
+               tracker:       tracker,
+       }
+}
+
+func (t *teeReader) Read(p []byte) (n int, err error) {
+       n, err = t.reader.Read(p)
+
+       // Read encountered error
+       if err != nil && err != io.EOF {
+               event := newProgressEvent(TransferFailedEvent, t.consumedBytes, t.totalBytes, 0)
+               publishProgress(t.listener, event)
+       }
+
+       if n > 0 {
+               t.consumedBytes += int64(n)
+               // CRC
+               if t.writer != nil {
+                       if n, err := t.writer.Write(p[:n]); err != nil {
+                               return n, err
+                       }
+               }
+               // Progress
+               if t.listener != nil {
+                       event := newProgressEvent(TransferDataEvent, t.consumedBytes, t.totalBytes, int64(n))
+                       publishProgress(t.listener, event)
+               }
+               // Track
+               if t.tracker != nil {
+                       t.tracker.completedBytes = t.consumedBytes
+               }
+       }
+
+       return
+}
+
+func (t *teeReader) Close() error {
+       if rc, ok := t.reader.(io.ReadCloser); ok {
+               return rc.Close()
+       }
+       return nil
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress_test.go
new file mode 100644 (file)
index 0000000..f92ebe1
--- /dev/null
@@ -0,0 +1,582 @@
+// bucket test
+
+package oss
+
+import (
+       "bytes"
+       "io/ioutil"
+       "math/rand"
+       "os"
+       "strings"
+       "sync/atomic"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssProgressSuite struct {
+       client *Client
+       bucket *Bucket
+}
+
+var _ = Suite(&OssProgressSuite{})
+
+// SetUpSuite runs once when the suite starts running
+func (s *OssProgressSuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       s.client = client
+
+       s.client.CreateBucket(bucketName)
+
+       bucket, err := s.client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       s.bucket = bucket
+
+       testLogger.Println("test progress started")
+}
+
+// TearDownSuite runs before each test or benchmark starts running
+func (s *OssProgressSuite) TearDownSuite(c *C) {
+       // Abort multipart uploads
+       keyMarker := KeyMarker("")
+       uploadIDMarker := UploadIDMarker("")
+       for {
+               lmu, err := s.bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+               c.Assert(err, IsNil)
+               for _, upload := range lmu.Uploads {
+                       imur := InitiateMultipartUploadResult{Bucket: bucketName, Key: upload.Key, UploadID: upload.UploadID}
+                       err = s.bucket.AbortMultipartUpload(imur)
+                       c.Assert(err, IsNil)
+               }
+               keyMarker = KeyMarker(lmu.NextKeyMarker)
+               uploadIDMarker = UploadIDMarker(lmu.NextUploadIDMarker)
+               if !lmu.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete objects
+       marker := Marker("")
+       for {
+               lor, err := s.bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = s.bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete bucket
+       err := s.client.DeleteBucket(s.bucket.BucketName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("test progress completed")
+}
+
+// SetUpTest runs after each test or benchmark runs
+func (s *OssProgressSuite) SetUpTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".txt")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".html")
+       c.Assert(err, IsNil)
+}
+
+// TearDownTest runs once after all tests or benchmarks have finished running
+func (s *OssProgressSuite) TearDownTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".txt")
+       c.Assert(err, IsNil)
+
+       err = removeTempFiles("../oss", ".html")
+       c.Assert(err, IsNil)
+}
+
+// OssProgressListener is the progress listener
+type OssProgressListener struct {
+       TotalRwBytes int64
+}
+
+// ProgressChanged handles progress event
+func (listener *OssProgressListener) ProgressChanged(event *ProgressEvent) {
+       switch event.EventType {
+       case TransferStartedEvent:
+               testLogger.Printf("Transfer Started, ConsumedBytes: %d, TotalBytes %d.\n",
+                       event.ConsumedBytes, event.TotalBytes)
+       case TransferDataEvent:
+               atomic.AddInt64(&listener.TotalRwBytes, event.RwBytes)
+               testLogger.Printf("Transfer Data, ConsumedBytes: %d, TotalBytes %d, %d%%.\n",
+                       event.ConsumedBytes, event.TotalBytes, event.ConsumedBytes*100/event.TotalBytes)
+       case TransferCompletedEvent:
+               testLogger.Printf("Transfer Completed, ConsumedBytes: %d, TotalBytes %d.\n",
+                       event.ConsumedBytes, event.TotalBytes)
+       case TransferFailedEvent:
+               testLogger.Printf("Transfer Failed, ConsumedBytes: %d, TotalBytes %d.\n",
+                       event.ConsumedBytes, event.TotalBytes)
+       default:
+       }
+}
+
+// TestPutObject
+func (s *OssProgressSuite) TestPutObject(c *C) {
+       objectName := RandStr(8) + ".jpg"
+       localFile := "../sample/The Go Programming Language.html"
+
+       fileInfo, err := os.Stat(localFile)
+       c.Assert(err, IsNil)
+
+       // PutObject
+       fd, err := os.Open(localFile)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       progressListener := OssProgressListener{}
+       err = s.bucket.PutObject(objectName, fd, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       // PutObjectFromFile
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.PutObjectFromFile(objectName, localFile, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       // DoPutObject
+       fd, err = os.Open(localFile)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       request := &PutObjectRequest{
+               ObjectKey: objectName,
+               Reader:    fd,
+       }
+
+       progressListener.TotalRwBytes = 0
+       options := []Option{Progress(&progressListener)}
+       _, err = s.bucket.DoPutObject(request, options)
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       // PutObject size is 0
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.PutObject(objectName, strings.NewReader(""), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, int64(0))
+
+       testLogger.Println("OssProgressSuite.TestPutObject")
+}
+
+// TestSignURL
+func (s *OssProgressSuite) SignURLTestFunc(c *C, authVersion AuthVersionType, extraHeaders []string) {
+       objectName := objectNamePrefix + RandStr(8)
+       filePath := RandLowStr(10)
+       content := RandStr(20)
+       CreateFile(filePath, content, c)
+
+       oldType := s.bucket.Client.Config.AuthVersion
+       oldHeaders := s.bucket.Client.Config.AdditionalHeaders
+       s.bucket.Client.Config.AuthVersion = authVersion
+       s.bucket.Client.Config.AdditionalHeaders = extraHeaders
+
+       // Sign URL for put
+       progressListener := OssProgressListener{}
+       str, err := s.bucket.SignURL(objectName, HTTPPut, 60, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Put object with URL
+       fd, err := os.Open(filePath)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       err = s.bucket.PutObjectWithURL(str, fd, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, int64(len(content)))
+
+       // Put object from file with URL
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.PutObjectFromFileWithURL(str, filePath, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, int64(len(content)))
+
+       // DoPutObject
+       fd, err = os.Open(filePath)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       progressListener.TotalRwBytes = 0
+       options := []Option{Progress(&progressListener)}
+       _, err = s.bucket.DoPutObjectWithURL(str, fd, options)
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, int64(len(content)))
+
+       // Sign URL for get
+       str, err = s.bucket.SignURL(objectName, HTTPGet, 60, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       if s.bucket.Client.Config.AuthVersion == AuthV1 {
+               c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true)
+       } else {
+               c.Assert(strings.Contains(str, HTTPParamSignatureVersion+"=OSS2"), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamExpiresV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamAccessKeyIDV2+"="), Equals, true)
+               c.Assert(strings.Contains(str, HTTPParamSignatureV2+"="), Equals, true)
+       }
+
+       // Get object with URL
+       progressListener.TotalRwBytes = 0
+       body, err := s.bucket.GetObjectWithURL(str, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       str, err = readBody(body)
+       c.Assert(err, IsNil)
+       c.Assert(str, Equals, content)
+       c.Assert(progressListener.TotalRwBytes, Equals, int64(len(content)))
+
+       // Get object to file with URL
+       progressListener.TotalRwBytes = 0
+       str, err = s.bucket.SignURL(objectName, HTTPGet, 10, Progress(&progressListener))
+       c.Assert(err, IsNil)
+
+       newFile := RandStr(10)
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.GetObjectToFileWithURL(str, newFile, Progress(&progressListener))
+       c.Assert(progressListener.TotalRwBytes, Equals, int64(len(content)))
+       c.Assert(err, IsNil)
+       eq, err := compareFiles(filePath, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(filePath)
+       os.Remove(newFile)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("OssProgressSuite.TestSignURL")
+
+       s.bucket.Client.Config.AuthVersion = oldType
+       s.bucket.Client.Config.AdditionalHeaders = oldHeaders
+}
+
+func (s *OssProgressSuite) TestSignURL(c *C) {
+       s.SignURLTestFunc(c, AuthV1, []string{})
+       s.SignURLTestFunc(c, AuthV2, []string{})
+       s.SignURLTestFunc(c, AuthV2, []string{"host", "range", "user-agent"})
+}
+
+func (s *OssProgressSuite) TestPutObjectNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       localFile := "../sample/The Go Programming Language.html"
+
+       // Invalid endpoint
+       client, err := New("http://oss-cn-taikang.aliyuncs.com", accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+
+       err = bucket.PutObjectFromFile(objectName, localFile, Progress(&OssProgressListener{}))
+       testLogger.Println(err)
+       c.Assert(err, NotNil)
+
+       testLogger.Println("OssProgressSuite.TestPutObjectNegative")
+}
+
+// TestAppendObject
+func (s *OssProgressSuite) TestAppendObject(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       objectValue := RandStr(100)
+       var val = []byte(objectValue)
+       var nextPos int64
+       var midPos = 1 + rand.Intn(len(val)-1)
+
+       // AppendObject
+       progressListener := OssProgressListener{}
+       nextPos, err := s.bucket.AppendObject(objectName, bytes.NewReader(val[0:midPos]), nextPos, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, nextPos)
+
+       // DoAppendObject
+       request := &AppendObjectRequest{
+               ObjectKey: objectName,
+               Reader:    bytes.NewReader(val[midPos:]),
+               Position:  nextPos,
+       }
+       options := []Option{Progress(&OssProgressListener{})}
+       _, err = s.bucket.DoAppendObject(request, options)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("OssProgressSuite.TestAppendObject")
+}
+
+// TestMultipartUpload
+func (s *OssProgressSuite) TestMultipartUpload(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+       fileInfo, err := os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       chunks, err := SplitFileByPartNum(fileName, 3)
+       c.Assert(err, IsNil)
+       testLogger.Println("chunks:", chunks)
+
+       fd, err := os.Open(fileName)
+       c.Assert(err, IsNil)
+       defer fd.Close()
+
+       // Initiate
+       progressListener := OssProgressListener{}
+       imur, err := s.bucket.InitiateMultipartUpload(objectName)
+       c.Assert(err, IsNil)
+
+       // UploadPart
+       var parts []UploadPart
+       for _, chunk := range chunks {
+               fd.Seek(chunk.Offset, os.SEEK_SET)
+               part, err := s.bucket.UploadPart(imur, fd, chunk.Size, chunk.Number, Progress(&progressListener))
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       // Complete
+       _, err = s.bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("OssProgressSuite.TestMultipartUpload")
+}
+
+// TestMultipartUploadFromFile
+func (s *OssProgressSuite) TestMultipartUploadFromFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+       fileInfo, err := os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       chunks, err := SplitFileByPartNum(fileName, 3)
+       c.Assert(err, IsNil)
+
+       // Initiate
+       imur, err := s.bucket.InitiateMultipartUpload(objectName)
+       c.Assert(err, IsNil)
+
+       // UploadPart
+       progressListener := OssProgressListener{}
+       var parts []UploadPart
+       for _, chunk := range chunks {
+               part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, chunk.Number, Progress(&progressListener))
+               c.Assert(err, IsNil)
+               parts = append(parts, part)
+       }
+
+       // Complete
+       _, err = s.bucket.CompleteMultipartUpload(imur, parts)
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("OssProgressSuite.TestMultipartUploadFromFile")
+}
+
+// TestGetObject
+func (s *OssProgressSuite) TestGetObject(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       localFile := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := "newpic-progress-1.jpg"
+
+       fileInfo, err := os.Stat(localFile)
+       c.Assert(err, IsNil)
+
+       progressListener := OssProgressListener{}
+       // PutObject
+       err = s.bucket.PutObjectFromFile(objectName, localFile, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       // GetObject
+       progressListener.TotalRwBytes = 0
+       body, err := s.bucket.GetObject(objectName, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       _, err = ioutil.ReadAll(body)
+       c.Assert(err, IsNil)
+       body.Close()
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       // GetObjectToFile
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.GetObjectToFile(objectName, newFile, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       // DoGetObject
+       progressListener.TotalRwBytes = 0
+       request := &GetObjectRequest{objectName}
+       options := []Option{Progress(&progressListener)}
+       result, err := s.bucket.DoGetObject(request, options)
+       c.Assert(err, IsNil)
+       _, err = ioutil.ReadAll(result.Response.Body)
+       c.Assert(err, IsNil)
+       result.Response.Body.Close()
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       // GetObject with range
+       progressListener.TotalRwBytes = 0
+       body, err = s.bucket.GetObject(objectName, Range(1024, 4*1024), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       text, err := ioutil.ReadAll(body)
+       c.Assert(err, IsNil)
+       body.Close()
+       c.Assert(progressListener.TotalRwBytes, Equals, int64(len(text)))
+
+       // PutObject size is 0
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.PutObject(objectName, strings.NewReader(""), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, int64(0))
+
+       // GetObject size is 0
+       progressListener.TotalRwBytes = 0
+       body, err = s.bucket.GetObject(objectName, Progress(&progressListener))
+       c.Assert(err, IsNil)
+       _, err = ioutil.ReadAll(body)
+       c.Assert(err, IsNil)
+       body.Close()
+       c.Assert(progressListener.TotalRwBytes, Equals, int64(0))
+
+       testLogger.Println("OssProgressSuite.TestGetObject")
+}
+
+// TestGetObjectNegative
+func (s *OssProgressSuite) TestGetObjectNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       localFile := "../sample/BingWallpaper-2015-11-07.jpg"
+
+       // PutObject
+       err := s.bucket.PutObjectFromFile(objectName, localFile)
+       c.Assert(err, IsNil)
+
+       // GetObject
+       body, err := s.bucket.GetObject(objectName, Progress(&OssProgressListener{}))
+       c.Assert(err, IsNil)
+
+       buf := make([]byte, 4*1024)
+       n, err := body.Read(buf)
+       c.Assert(err, IsNil)
+
+       //time.Sleep(70 * time.Second) TODO
+
+       // Read should fail
+       for err == nil {
+               n, err = body.Read(buf)
+               n += n
+       }
+       c.Assert(err, NotNil)
+       body.Close()
+
+       testLogger.Println("OssProgressSuite.TestGetObjectNegative")
+}
+
+// TestUploadFile
+func (s *OssProgressSuite) TestUploadFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+
+       fileInfo, err := os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       progressListener := OssProgressListener{}
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(5), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, objectName+".cp"), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       testLogger.Println("OssProgressSuite.TestUploadFile")
+}
+
+// TestDownloadFile
+func (s *OssProgressSuite) TestDownloadFile(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := "down-new-file-progress-2.jpg"
+
+       fileInfo, err := os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       // Upload
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       progressListener := OssProgressListener{}
+       err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(5), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.DownloadFile(objectName, newFile, 1024*1024, Routines(3), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.DownloadFile(objectName, newFile, 50*1024, Routines(3), Checkpoint(true, ""), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       testLogger.Println("OssProgressSuite.TestDownloadFile")
+}
+
+// TestCopyFile
+func (s *OssProgressSuite) TestCopyFile(c *C) {
+       srcObjectName := objectNamePrefix + RandStr(8)
+       destObjectName := srcObjectName + "-copy"
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+
+       fileInfo, err := os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       // Upload
+       progressListener := OssProgressListener{}
+       err = s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(5), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       progressListener.TotalRwBytes = 0
+       err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(3), Checkpoint(true, ""), Progress(&progressListener))
+       c.Assert(err, IsNil)
+       c.Assert(progressListener.TotalRwBytes, Equals, fileInfo.Size())
+
+       testLogger.Println("OssProgressSuite.TestCopyFile")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/redirect_1_6.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/redirect_1_6.go
new file mode 100644 (file)
index 0000000..d09bc5e
--- /dev/null
@@ -0,0 +1,11 @@
+// +build !go1.7
+
+package oss
+
+import "net/http"
+
+// http.ErrUseLastResponse only is defined go1.7 onward
+
+func disableHTTPRedirect(client *http.Client) {
+
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/redirect_1_7.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/redirect_1_7.go
new file mode 100644 (file)
index 0000000..5b0bb86
--- /dev/null
@@ -0,0 +1,12 @@
+// +build go1.7
+
+package oss
+
+import "net/http"
+
+// http.ErrUseLastResponse only is defined go1.7 onward
+func disableHTTPRedirect(client *http.Client) {
+       client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+               return http.ErrUseLastResponse
+       }
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_csv_objcet_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_csv_objcet_test.go
new file mode 100644 (file)
index 0000000..d406856
--- /dev/null
@@ -0,0 +1,627 @@
+package oss
+
+import (
+       "io/ioutil"
+       "os"
+       "strconv"
+       "strings"
+       "io"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssSelectCsvSuite struct {
+       client *Client
+       bucket *Bucket
+}
+
+var _ = Suite(&OssSelectCsvSuite{})
+
+func (s *OssSelectCsvSuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       s.client = client
+       s.client.Config.LogLevel = Error // Debug
+       // s.client.Config.Timeout = 5
+       err = s.client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := s.client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       s.bucket = bucket
+
+       testLogger.Println("test select csv started")
+}
+
+func (s *OssSelectCsvSuite) TearDownSuite(c *C) {
+       // Delete objects
+       marker := Marker("")
+       for {
+               lor, err := s.bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = s.bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       err := s.client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("test select csv completed")
+}
+
+func (s *OssSelectCsvSuite) SetUpTest(c *C) {
+       testLogger.Println("test func", c.TestName(), "start")
+}
+
+func (s *OssSelectCsvSuite) TearDownTest(c *C) {
+       testLogger.Println("test func", c.TestName(), "succeed")
+}
+
+// TestCreateSelectObjectMeta
+func (s *OssSelectCsvSuite) TestCreateSelectCsvObjectMeta(c *C) {
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       csvMeta := CsvMetaRequest{}
+       var bo bool
+       csvMeta.OverwriteIfExists = &bo
+       res, err := s.bucket.CreateSelectCsvObjectMeta(key, csvMeta)
+       c.Assert(err, IsNil)
+       l, err := readCsvLine(localCsvFile)
+       c.Assert(err, IsNil)
+       c.Assert(res.RowsCount, Equals, int64(l))
+
+       bo = true
+       csvMeta.OverwriteIfExists = &bo
+       csvMeta.InputSerialization.CSV.RecordDelimiter = "\n"
+       csvMeta.InputSerialization.CSV.FieldDelimiter = ","
+       csvMeta.InputSerialization.CSV.QuoteCharacter = "\""
+       res, err = s.bucket.CreateSelectCsvObjectMeta(key, csvMeta)
+       c.Assert(err, IsNil)
+       c.Assert(res.RowsCount, Equals, int64(l))
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectCsvObjectIsEmpty(c *C) {
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       csvMeta := CsvMetaRequest{}
+       _, err = s.bucket.CreateSelectCsvObjectMeta(key, csvMeta)
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression = "select Year, StateAbbr, CityName, PopulationCount from ossobject where CityName != ''"
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+
+       body, err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer body.Close()
+
+       p := make([]byte, 512)
+       n, err := body.Read(p)
+       c.Assert(err, IsNil)
+       c.Assert(n, Equals, 512)
+       p1 := make([]byte, 3)
+       _, err = body.Read(p1)
+       c.Assert(err, IsNil)
+       rets, err := ioutil.ReadAll(body)
+       c.Assert(err, IsNil)
+       str, err := readCsvIsEmpty(localCsvFile)
+       c.Assert(err, IsNil)
+       c.Assert(string(p)+string(p1)+string(rets), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectObjectIntoFile(c *C) {
+       var bo bool = true
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       csvMeta := CsvMetaRequest{
+               InputSerialization: InputSerialization {
+                       CSV: CSV {
+                               RecordDelimiter: "\n",
+                               FieldDelimiter: ",",
+                               QuoteCharacter: "\"",
+                       },
+               },
+               OverwriteIfExists: &bo,
+       }
+       res, err := s.bucket.CreateSelectCsvObjectMeta(key, csvMeta)
+       c.Assert(err, IsNil)
+       l, err := readCsvLine(localCsvFile)
+       c.Assert(err, IsNil)
+       c.Assert(res.RowsCount, Equals, int64(l))
+
+       selReq := SelectRequest{
+               Expression:"select * from ossobject",
+               InputSerializationSelect: InputSerializationSelect {
+                       CsvBodyInput :CSVSelectInput{
+                               FileHeaderInfo: "None",
+                               CommentCharacter: "#",
+                               RecordDelimiter: "\n",
+                               FieldDelimiter: ",",
+                               QuoteCharacter:"\"",
+                               Range:"",
+                       },
+               },
+       }
+       outfile := "sample_data_out.csv"
+       err = s.bucket.SelectObjectIntoFile(key, outfile, selReq)
+       c.Assert(err, IsNil)
+
+       fd1, err := os.Open(outfile)
+       c.Assert(err,IsNil)
+       defer fd1.Close()
+       fd2, err := os.Open(localCsvFile)
+       c.Assert(err,IsNil)
+       defer fd2.Close()
+       str1, err := ioutil.ReadAll(fd1)
+       c.Assert(err,IsNil)
+       str2 ,err := ioutil.ReadAll(fd2)
+       c.Assert(err,IsNil)
+       c.Assert(string(str1), Equals, string(str2))
+       
+       err = os.Remove(outfile)
+       c.Assert(err, IsNil)
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func(s *OssSelectCsvSuite) TestSelectCsvObjectRange(c *C) {
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       csvMeta := CsvMetaRequest{}
+       _,err = s.bucket.CreateSelectCsvObjectMeta(key, csvMeta)
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression = "select Year,StateAbbr, CityName, Short_Question_Text from ossobject"
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+       selReq.InputSerializationSelect.CsvBodyInput.Range = "0-2"
+       body, err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer body.Close()
+       rets, err := ioutil.ReadAll(body)
+
+       str,err := readCsvRange(localCsvFile, 0, 2)
+       c.Assert(err, IsNil)
+       c.Assert(string(rets), Equals, str)
+       
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func(s *OssSelectCsvSuite) TestSelectCsvObjectLike(c *C) {
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  "select Year, StateAbbr, CityName, Short_Question_Text from ossobject where Measure like '%blood pressure%Years'"
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       str, err := readCsvLike(localCsvFile)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func(s *OssSelectCsvSuite) TestSelectCsvObjectIntAggregation(c *C) {
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select avg(cast(year as int)), max(cast(year as int)), min(cast(year as int)) from ossobject where year = 2015`
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+
+       c.Assert(string(ts), Equals, "2015,2015,2015\n")
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func(s *OssSelectCsvSuite) TestSelectCsvObjectFloatAggregation(c *C) {
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select avg(cast(data_value as double)), max(cast(data_value as double)), sum(cast(data_value as double)) from ossobject`
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       strR := string(ts)
+       c.Assert(err, IsNil)
+
+       avg, max, sum , err := readCsvFloatAgg(localCsvFile)
+       c.Assert(err, IsNil)
+       
+       s1 := strconv.FormatFloat(avg, 'f', 5, 32) + ","
+       s1 += strconv.FormatFloat(max, 'f', 5, 32) + ","
+       s1 += strconv.FormatFloat(sum, 'f', 5, 32) + ","
+       retS := ""
+       for _, v := range strings.Split(strR[:len(strR)-1], ",") {
+               vv, err := strconv.ParseFloat(v, 64)
+               c.Assert(err, IsNil)
+               retS += strconv.FormatFloat(vv, 'f', 5, 32) + ","
+       }
+       c.Assert(s1, Equals, retS)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func(s *OssSelectCsvSuite) TestSelectCsvObjectConcat(c *C) {
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select Year,StateAbbr, CityName, Short_Question_Text from ossobject where (data_value || data_value_unit) = '14.8%'`
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+
+       str, err := readCsvConcat(localCsvFile)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectCsvObjectComplicateConcat(c *C) {
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `
+       select 
+               Year,StateAbbr, CityName, Short_Question_Text, data_value, 
+               data_value_unit, category, high_confidence_limit 
+       from 
+               ossobject 
+       where 
+               data_value > 14.8 and 
+               data_value_unit = '%' or 
+               Measure like '%18 Years' and 
+               Category = 'Unhealthy Behaviors' or 
+               high_confidence_limit > 70.0 `
+       
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+
+       str, err := readCsvComplicateCondition(localCsvFile)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectCsvObjectInvalidSql(c *C) {
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select * from ossobject where avg(cast(year as int)) > 2016`
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression =  ``
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression =  `select year || CityName from ossobject`
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression =  `select * from ossobject group by CityName`
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression =  `select * from ossobject order by _1`
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression =  `select * from ossobject oss join s3object s3 on oss.CityName = s3.CityName`
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression =  `select _1 from ossobject`
+       ret, err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       _, err = ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectCsvObjectWithOutputDelimiters(c *C) {
+       key := "sample_data.csv"
+       content := "abc,def\n"
+       err := s.bucket.PutObject(key, strings.NewReader(content))
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select _1, _2 from ossobject `
+       selReq.OutputSerializationSelect.CsvBodyOutput.RecordDelimiter = "\r\n"
+       selReq.OutputSerializationSelect.CsvBodyOutput.FieldDelimiter = "|"
+
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, "abc|def\r\n")
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectCsvObjectWithCrc(c *C) {
+       key := "sample_data.csv"
+       content := "abc,def\n"
+       err := s.bucket.PutObject(key, strings.NewReader(content))
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select * from ossobject`
+       bo := true
+       selReq.OutputSerializationSelect.EnablePayloadCrc = &bo
+
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, content)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectCsvObjectWithSkipPartialData(c *C) {
+       key := "sample_data.csv"
+       content := "abc,def\nefg\n"
+       err := s.bucket.PutObject(key, strings.NewReader(content))
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select _1, _2 from ossobject`
+       bo := true
+       selReq.SelectOptions.SkipPartialDataRecord = &bo
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, "abc,def\n")
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectCsvObjectWithOutputRaw(c *C) {
+       key := "sample_data.csv"
+       content := "abc,def\n"
+       err := s.bucket.PutObject(key, strings.NewReader(content))
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select _1 from ossobject`
+       bo := true
+       selReq.OutputSerializationSelect.OutputRawData = &bo
+
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, "abc\n")
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectCsvObjectWithKeepColumns(c *C) {
+       key := "sample_data.csv"
+       content := "abc,def\n"
+       err := s.bucket.PutObject(key, strings.NewReader(content))
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select _1 from ossobject`
+       bo := true
+       selReq.OutputSerializationSelect.KeepAllColumns = &bo
+
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, "abc,\n")
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectCsvObjectWithOutputHeader(c *C) {
+       key := "sample_data.csv"
+       content := "name,job\nabc,def\n"
+       err := s.bucket.PutObject(key, strings.NewReader(content))
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select name from ossobject`
+       bo := true
+       selReq.OutputSerializationSelect.OutputHeader = &bo
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, "name\nabc\n")
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectCsvSuite) TestSelectCsvObjectRead(c *C) {
+       key := "sample_data.csv"
+       content := "name,job\nabc,def\n"
+       err := s.bucket.PutObject(key, strings.NewReader(content))
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select name from ossobject`
+       bo := true
+       selReq.OutputSerializationSelect.OutputHeader = &bo
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+       selReq.OutputSerializationSelect.EnablePayloadCrc = &bo
+
+       ret,err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+
+       // case 1: read length > data length
+       p := make([]byte, 512)
+       n, err := ret.Read(p[:20])
+       if err != nil && err != io.EOF {
+               c.Assert(err, IsNil)
+       }
+       c.Assert(string(p[:n]), Equals, "name\nabc\n")
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, "")
+
+       // case 2: read length = data length
+       ret,err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       n, err = ret.Read(p[:9])
+       if err != nil && err != io.EOF {
+               c.Assert(err, IsNil)
+       }
+       c.Assert(string(p[:n]), Equals, "name\nabc\n")
+       ts, err = ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, "")
+
+       // case 3: read length > one frame length and read length < two frame, (this data = 2 * frame length)
+       ret,err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       n, err = ret.Read(p[:7])
+       if err != nil && err != io.EOF {
+               c.Assert(err, IsNil)
+       }
+       c.Assert(string(p[:n]), Equals, "name\nab")
+       ts, err = ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, "c\n")
+
+       // case 4: read length = a frame length (this data = 2 * frame length)
+       ret,err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       n, err = ret.Read(p[:5])
+       if err != nil && err != io.EOF {
+               c.Assert(err, IsNil)
+       }
+       c.Assert(string(p[:n]), Equals, "name\n")
+       ts, err = ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, "abc\n")
+
+       // case 5: read length < a frame length (this data = 2 * frame length)
+       ret,err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       n, err = ret.Read(p[:3])
+       if err != nil && err != io.EOF {
+               c.Assert(err, IsNil)
+       }
+       c.Assert(string(p[:n]), Equals, "nam")
+       ts, err = ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, "e\nabc\n")
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+// OssProgressListener is the progress listener
+type OssSelectProgressListener struct {
+}
+// ProgressChanged handles progress event
+func (listener *OssSelectProgressListener) ProgressChanged(event *ProgressEvent) {
+       switch event.EventType {
+       case TransferStartedEvent:
+               testLogger.Printf("Transfer Started.\n")
+       case TransferDataEvent:
+               testLogger.Printf("Transfer Data, This time consumedBytes: %d \n", event.ConsumedBytes)
+       case TransferCompletedEvent:
+               testLogger.Printf("Transfer Completed, This time consumedBytes: %d.\n", event.ConsumedBytes)
+       case TransferFailedEvent:
+               testLogger.Printf("Transfer Failed, This time consumedBytes: %d.\n", event.ConsumedBytes)
+       default:
+       }
+}
+
+func(s *OssSelectCsvSuite) TestSelectCsvObjectConcatProgress(c *C) {
+       key := "sample_data.csv"
+       localCsvFile := "../sample/sample_data.csv"
+       err := s.bucket.PutObjectFromFile(key, localCsvFile)
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression =  `select Year,StateAbbr, CityName, Short_Question_Text from ossobject where (data_value || data_value_unit) = '14.8%'`
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+       ret,err := s.bucket.SelectObject(key, selReq, Progress(&OssSelectProgressListener{}))
+       c.Assert(err, IsNil)
+       defer ret.Close()
+       ts, err := ioutil.ReadAll(ret)
+       c.Assert(err, IsNil)
+
+       str, err := readCsvConcat(localCsvFile)
+       c.Assert(err, IsNil)
+       c.Assert(string(ts), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_json_object_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_json_object_test.go
new file mode 100644 (file)
index 0000000..e4af98c
--- /dev/null
@@ -0,0 +1,488 @@
+package oss
+
+import (
+       . "gopkg.in/check.v1"
+       "os"
+       "io/ioutil"
+       "strings"
+       "net/http"
+)
+
+type OssSelectJsonSuite struct {
+       client *Client
+       bucket *Bucket
+}
+
+var _ = Suite(&OssSelectJsonSuite{})
+
+func (s *OssSelectJsonSuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       s.client = client
+       s.client.Config.LogLevel = Error // Debug
+       err = s.client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := s.client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       s.bucket = bucket
+
+       testLogger.Println("test select json started")
+}
+
+func(s *OssSelectJsonSuite) TearDownSuite(c *C){
+       // Delete objects
+       marker := Marker("")
+       for {
+               lor, err := s.bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = s.bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       err := s.client.DeleteBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("test select json completed")
+}
+
+func (s *OssSelectJsonSuite) SetUpTest(c *C) {
+       testLogger.Println("test func", c.TestName(), "start")
+}
+
+func (s *OssSelectJsonSuite) TearDownTest(c *C) {
+       testLogger.Println("test func", c.TestName(), "succeed")
+}
+
+func (s *OssSelectJsonSuite) TestCreateSelectJsonObjectMeta(c *C){
+       key := "sample_json_lines.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json_lines.json")
+       c.Assert(err, IsNil)
+       jsonMeta := JsonMetaRequest{
+               InputSerialization: InputSerialization {
+                       JSON: JSON {
+                               JSONType:"LINES",
+                       },
+               },
+       }
+       res,err := s.bucket.CreateSelectJsonObjectMeta(key, jsonMeta)
+       c.Assert(err, IsNil)
+       c.Assert(res.RowsCount, Equals, int64(100))
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonDocument(c *C){
+       key := "sample_json.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json.json")
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression = "select * from ossobject.objects[*] where party = 'Democrat'"
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "DOCUMENT"
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+
+       var responseHeader http.Header
+       body, err := s.bucket.SelectObject(key, selReq, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       defer body.Close()
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       p := make([]byte, 512)
+       n, err := body.Read(p)
+       c.Assert(err, IsNil)
+       c.Assert(n, Equals, 512)
+       p1 := make([]byte, 3)
+       _, err = body.Read(p1)
+       c.Assert(err, IsNil)
+       rets, err := ioutil.ReadAll(body)
+       c.Assert(err,IsNil)
+       str,err := readJsonDocument("../sample/sample_json.json")
+       c.Assert(err, IsNil)
+       c.Assert(string(p) + string(p1) + string(rets), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonLines(c *C) {
+       key := "sample_json_lines.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json_lines.json")
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression = "select * from ossobject where party = 'Democrat'"
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "LINES"
+
+       var responseHeader http.Header
+       body, err := s.bucket.SelectObject(key, selReq, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       defer body.Close()
+       
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       rets, err := ioutil.ReadAll(body)
+       c.Assert(err,IsNil)
+       str,err := readJsonDocument("../sample/sample_json.json")
+       c.Assert(string(rets), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonLinesIntoFile(c *C) {
+       key := "sample_json_lines.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json_lines.json")
+       c.Assert(err, IsNil)
+       
+       jsonMeta := JsonMetaRequest{
+               InputSerialization: InputSerialization {
+                       JSON: JSON {
+                               JSONType:"LINES",
+                       },
+               },
+       }
+       res,err := s.bucket.CreateSelectJsonObjectMeta(key, jsonMeta)
+       c.Assert(err, IsNil)
+       c.Assert(res.RowsCount, Equals, int64(100))
+
+       selReq := SelectRequest{}
+       selReq.Expression = "select * from ossobject where party = 'Democrat'"
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "LINES"
+
+       var responseHeader http.Header
+       outfile := "sample_json_out.json"
+       err = s.bucket.SelectObjectIntoFile(key, outfile, selReq, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       _, err = os.Stat(outfile)
+       c.Assert(err,IsNil)
+       err = os.Remove(outfile)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonDocumentIntoFile(c *C) {
+       key := "sample_json_lines.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json.json")
+       c.Assert(err, IsNil)
+
+       selReq := SelectRequest{}
+       selReq.Expression = "select * from ossobject.objects[*] where party = 'Democrat'"
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "DOCUMENT"
+
+       var responseHeader http.Header
+       outfile := "sample_json_out.json"
+       err = s.bucket.SelectObjectIntoFile(key, outfile, selReq, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       _, err = os.Stat(outfile)
+       c.Assert(err,IsNil)
+       err = os.Remove(outfile)
+       c.Assert(err, IsNil)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonLinesLike(c *C) {
+       key := "sample_json_lines.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json_lines.json")
+       c.Assert(err, IsNil)
+       selReq := SelectRequest{}
+       selReq.Expression = "select person.firstname, person.lastname from ossobject where person.birthday like '1959%'"
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "LINES"
+
+       jsonMeta := JsonMetaRequest{
+               InputSerialization: InputSerialization {
+                       JSON: JSON {
+                               JSONType:"LINES",
+                       },
+               },
+       }
+       res,err := s.bucket.CreateSelectJsonObjectMeta(key, jsonMeta)
+       c.Assert(err, IsNil)
+       c.Assert(res.RowsCount, Equals, int64(100))
+
+       var responseHeader http.Header
+       body, err := s.bucket.SelectObject(key, selReq, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       defer body.Close()
+       
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       rets, err := ioutil.ReadAll(body)
+       c.Assert(err,IsNil)
+       str,err := readJsonLinesLike("../sample/sample_json.json")
+       c.Assert(string(rets), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonLinesRange(c *C) {
+       key := "sample_json_lines.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json_lines.json")
+       c.Assert(err, IsNil)
+       jsonMeta := JsonMetaRequest{
+               InputSerialization: InputSerialization {
+                       JSON: JSON {
+                               JSONType:"LINES",
+                       },
+               },
+       }
+       res,err := s.bucket.CreateSelectJsonObjectMeta(key, jsonMeta)
+       c.Assert(err, IsNil)
+       c.Assert(res.RowsCount, Equals, int64(100))
+
+       selReq := SelectRequest{}
+       selReq.Expression = "select person.firstname as aaa as firstname, person.lastname, extra from ossobject'"
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "LINES"
+       selReq.InputSerializationSelect.JsonBodyInput.Range = "0-1"
+       
+       var responseHeader http.Header
+       body, err := s.bucket.SelectObject(key, selReq, GetResponseHeader(&responseHeader))
+       c.Assert(err, IsNil)
+       defer body.Close()
+       
+       requestId := GetRequestId(responseHeader)
+       c.Assert(len(requestId) > 0, Equals, true)
+
+       rets, err := ioutil.ReadAll(body)
+       c.Assert(err,IsNil)
+       str,err := readJsonLinesRange("../sample/sample_json.json", 0, 2)
+       c.Assert(string(rets), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonDocumentIntAggregation(c *C) {
+       key := "sample_json.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json.json")
+       c.Assert(err, IsNil)
+
+       selReq := SelectRequest{}
+       selReq.Expression = `
+       select 
+               avg(cast(person.cspanid as int)), max(cast(person.cspanid as int)), 
+               min(cast(person.cspanid as int)) 
+       from 
+               ossobject.objects[*] 
+       where 
+               person.cspanid = 1011723
+       `
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "Document"
+       
+       body, err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer body.Close()
+
+       rets, err := ioutil.ReadAll(body)
+       c.Assert(err,IsNil)
+       c.Assert(string(rets), Equals, "{\"_1\":1011723,\"_2\":1011723,\"_3\":1011723},")
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonDocumentFloatAggregation(c *C) {
+       key := "sample_json.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json.json")
+       c.Assert(err, IsNil)
+
+       selReq := SelectRequest{}
+       selReq.Expression = `
+       select 
+               avg(cast(person.cspanid as double)), max(cast(person.cspanid as double)), 
+               min(cast(person.cspanid as double)) 
+       from 
+               ossobject.objects[*]
+       `
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "Document"
+       
+       body, err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer body.Close()
+
+       rets, err := ioutil.ReadAll(body)
+       c.Assert(err,IsNil)
+       testLogger.Println(string(rets))
+       // avg, max, min, err := readJsonFloatAggregation("../sample/sample_json.json")
+       // fmt.Println(string(rets), "\n", avg, max, min)
+       // retsArr := strings.Split(string(rets), ":")
+       // s1 := strconv.FormatFloat(avg, 'f', 6, 64) + ","
+       // s1 += strconv.FormatFloat(max, 'f', 6, 64) + ","
+       // s1 += strconv.FormatFloat(min, 'f', 6, 64) + ","
+       // retS := ""
+       // l := len(retsArr[1])
+       // vv, err := strconv.ParseFloat(retsArr[1][:l-35], 64)
+       // c.Assert(err, IsNil)
+       // retS += strconv.FormatFloat(vv, 'f', 6, 64) + ","
+       // l = len(retsArr[2])
+       // vv, err = strconv.ParseFloat(retsArr[2][:l-6], 64)
+       // c.Assert(err, IsNil)
+       // retS += strconv.FormatFloat(vv, 'f', 6, 64) + ","
+       // l = len(retsArr[3])
+       // vv, err = strconv.ParseFloat(retsArr[3][:l-2], 64)
+       // c.Assert(err, IsNil)
+       // retS += strconv.FormatFloat(vv, 'f', 6, 64) + ","
+       // c.Assert(retS, Equals, s1)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonDocumentConcat(c *C) {
+       key := "sample_json.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json.json")
+       c.Assert(err, IsNil)
+
+       selReq := SelectRequest{}
+       selReq.Expression = `
+       select 
+               person 
+       from 
+               ossobject.objects[*] 
+       where 
+               (person.firstname || person.lastname) = 'JohnKennedy'
+       `
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "Document"
+       
+       body, err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer body.Close()
+
+       rets, err := ioutil.ReadAll(body)
+       c.Assert(err,IsNil)
+       str, err := readJsonDocumentConcat("../sample/sample_json.json")
+       c.Assert(err,IsNil)
+       c.Assert(string(rets), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonComplicateConcat(c *C) {
+       key := "sample_json.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json_lines.json")
+       c.Assert(err, IsNil)
+
+       selReq := SelectRequest{}
+       selReq.Expression = `
+       select 
+               person.firstname, person.lastname, congress_numbers 
+       from
+               ossobject 
+       where
+               startdate > '2017-01-01' and 
+               senator_rank = 'junior' or 
+               state = 'CA' and 
+               party = 'Republican'
+       `
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "LINES"
+       
+       body, err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer body.Close()
+
+       rets, err := ioutil.ReadAll(body)
+       c.Assert(err,IsNil)
+       str, err := readJsonComplicateConcat("../sample/sample_json.json")
+       c.Assert(err,IsNil)
+       c.Assert(string(rets), Equals, str)
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonLineInvalidSql(c *C) {
+       key := "sample_json.json"
+       err := s.bucket.PutObjectFromFile(key, "../sample/sample_json_lines.json")
+       c.Assert(err, IsNil)
+
+       selReq := SelectRequest{}
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "LINES"
+       
+       selReq.Expression = `select * from ossobject where avg(cast(person.birthday as int)) > 2016`
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression = ``
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression = `select person.lastname || person.firstname from ossobject`
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression = `select * from ossobject group by person.firstname`
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression = `select * from ossobject order by _1`
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       selReq.Expression = `select * from ossobject oss join s3object s3 on oss.CityName = s3.CityName`
+       _, err = s.bucket.SelectObject(key, selReq)
+       c.Assert(err, NotNil)
+
+       err = s.bucket.PutObjectFromFile(key, "../sample/sample_json.json")
+       c.Assert(err, IsNil)
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "DOCUMENT"
+       selReq.Expression = `select _1 from ossobject.objects[*]`
+       body, err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer body.Close()
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
+
+func (s *OssSelectJsonSuite) TestSelectJsonParseNumAsString(c *C) {
+       key := "sample_json.json"
+       content := "{\"a\":123456789.123456789}"
+       err := s.bucket.PutObject(key, strings.NewReader(content))
+       c.Assert(err, IsNil)
+
+       selReq := SelectRequest{}
+       selReq.Expression = `select a from ossobject where cast(a as decimal) = 123456789.1234567890`
+       bo := true
+       selReq.InputSerializationSelect.JsonBodyInput.ParseJSONNumberAsString = &bo
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "DOCUMENT"
+       
+       body, err := s.bucket.SelectObject(key, selReq)
+       c.Assert(err, IsNil)
+       defer body.Close()
+
+       rets, err := ioutil.ReadAll(body)
+       c.Assert(err,IsNil)
+       c.Assert(string(rets), Equals, "{\"a\":123456789.123456789}\n")
+
+       err = s.bucket.DeleteObject(key)
+       c.Assert(err, IsNil)
+}
\ No newline at end of file
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_object.go
new file mode 100644 (file)
index 0000000..2e0da46
--- /dev/null
@@ -0,0 +1,197 @@
+package oss
+
+import (
+       "bytes"
+       "encoding/xml"
+       "hash/crc32"
+       "io"
+       "io/ioutil"
+       "net/http"
+       "os"
+       "strings"
+)
+
+// CreateSelectCsvObjectMeta is Creating csv object meta
+//
+// key                 the object key.
+// csvMeta                     the csv file meta
+// options             the options for create csv Meta  of the object.
+//
+// MetaEndFrameCSV     the csv file meta info
+// error               it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) CreateSelectCsvObjectMeta(key string, csvMeta CsvMetaRequest, options ...Option) (MetaEndFrameCSV, error) {
+       var endFrame MetaEndFrameCSV
+       params := map[string]interface{}{}
+       params["x-oss-process"] = "csv/meta"
+
+       csvMeta.encodeBase64()
+       bs, err := xml.Marshal(csvMeta)
+       if err != nil {
+               return endFrame, err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       resp, err := bucket.DoPostSelectObject(key, params, buffer, options...)
+       if err != nil {
+               return endFrame, err
+       }
+       defer resp.Body.Close()
+
+       _, err = ioutil.ReadAll(resp)
+
+       return resp.Frame.MetaEndFrameCSV, err
+}
+
+// CreateSelectJsonObjectMeta is Creating json object meta
+//
+// key                         the object key.
+// csvMeta                             the json file meta
+// options                     the options for create json Meta  of the object.
+//
+// MetaEndFrameJSON    the json file meta info
+// error                       it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) CreateSelectJsonObjectMeta(key string, jsonMeta JsonMetaRequest, options ...Option) (MetaEndFrameJSON, error) {
+       var endFrame MetaEndFrameJSON
+       params := map[string]interface{}{}
+       params["x-oss-process"] = "json/meta"
+
+       bs, err := xml.Marshal(jsonMeta)
+       if err != nil {
+               return endFrame, err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+
+       resp, err := bucket.DoPostSelectObject(key, params, buffer, options...)
+       if err != nil {
+               return endFrame, err
+       }
+       defer resp.Body.Close()
+
+       _, err = ioutil.ReadAll(resp)
+
+       return resp.Frame.MetaEndFrameJSON, err
+}
+
+// SelectObject is the select object api, approve csv and json file.
+//
+// key                 the object key.
+// selectReq           the request data for select object
+// options             the options for select file of the object.
+//
+// o.ReadCloser        reader instance for reading data from response. It must be called close() after the usage and only valid when error is nil.
+// error               it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) SelectObject(key string, selectReq SelectRequest, options ...Option) (io.ReadCloser, error) {
+       params := map[string]interface{}{}
+       if selectReq.InputSerializationSelect.JsonBodyInput.JsonIsEmpty() {
+               params["x-oss-process"] = "csv/select" // default select csv file
+       } else {
+               params["x-oss-process"] = "json/select"
+       }
+       selectReq.encodeBase64()
+       bs, err := xml.Marshal(selectReq)
+       if err != nil {
+               return nil, err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+       resp, err := bucket.DoPostSelectObject(key, params, buffer, options...)
+       if err != nil {
+               return nil, err
+       }
+       if selectReq.OutputSerializationSelect.EnablePayloadCrc != nil && *selectReq.OutputSerializationSelect.EnablePayloadCrc == true {
+               resp.Frame.EnablePayloadCrc = true
+       }
+       resp.Frame.OutputRawData = strings.ToUpper(resp.Headers.Get("x-oss-select-output-raw")) == "TRUE"
+
+       return resp, err
+}
+
+// DoPostSelectObject is the SelectObject/CreateMeta api, approve csv and json file.
+//
+// key                         the object key.
+// params                              the resource of oss approve csv/meta, json/meta, csv/select, json/select.
+// buf                                 the request data trans to buffer.
+// options                     the options for select file of the object.
+//
+// SelectObjectResponse        the response of select object.
+// error                       it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) DoPostSelectObject(key string, params map[string]interface{}, buf *bytes.Buffer, options ...Option) (*SelectObjectResponse, error) {
+       resp, err := bucket.do("POST", key, params, options, buf, nil)
+       if err != nil {
+               return nil, err
+       }
+
+       result := &SelectObjectResponse{
+               Body:       resp.Body,
+               StatusCode: resp.StatusCode,
+               Frame:      SelectObjectResult{},
+       }
+       result.Headers = resp.Headers
+       // result.Frame = SelectObjectResult{}
+       result.ReadTimeOut = bucket.GetConfig().Timeout
+
+       // Progress
+       listener := GetProgressListener(options)
+
+       // CRC32
+       crcCalc := crc32.NewIEEE()
+       result.WriterForCheckCrc32 = crcCalc
+       result.Body = TeeReader(resp.Body, nil, 0, listener, nil)
+
+       err = CheckRespCode(resp.StatusCode, []int{http.StatusPartialContent, http.StatusOK})
+
+       return result, err
+}
+
+// SelectObjectIntoFile is the selectObject to file api
+//
+// key         the object key.
+// fileName            saving file's name to localstation.
+// selectReq           the request data for select object
+// options             the options for select file of the object.
+//
+// error       it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) SelectObjectIntoFile(key, fileName string, selectReq SelectRequest, options ...Option) error {
+       tempFilePath := fileName + TempFileSuffix
+
+       params := map[string]interface{}{}
+       if selectReq.InputSerializationSelect.JsonBodyInput.JsonIsEmpty() {
+               params["x-oss-process"] = "csv/select" // default select csv file
+       } else {
+               params["x-oss-process"] = "json/select"
+       }
+       selectReq.encodeBase64()
+       bs, err := xml.Marshal(selectReq)
+       if err != nil {
+               return err
+       }
+       buffer := new(bytes.Buffer)
+       buffer.Write(bs)
+       resp, err := bucket.DoPostSelectObject(key, params, buffer, options...)
+       if err != nil {
+               return err
+       }
+       defer resp.Close()
+
+       // If the local file does not exist, create a new one. If it exists, overwrite it.
+       fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, FilePermMode)
+       if err != nil {
+               return err
+       }
+
+       // Copy the data to the local file path.
+       _, err = io.Copy(fd, resp)
+       fd.Close()
+       if err != nil {
+               return err
+       }
+
+       return os.Rename(tempFilePath, fileName)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_object_read_file_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_object_read_file_test.go
new file mode 100644 (file)
index 0000000..7629087
--- /dev/null
@@ -0,0 +1,542 @@
+package oss
+
+import (
+       "bufio"
+       "io"
+       "os"
+       "strings"
+       "strconv"
+       "regexp"
+       "encoding/json"
+       "encoding/csv"
+)
+
+func handleError(err error) error {
+       if err == nil {
+               return nil
+       }
+       return err
+}
+
+func readCsvLine(fileName string) (int, error) {
+       file, err := os.Open(fileName)
+       if err != nil {
+               return 0,err
+       }
+       defer file.Close()
+       rd := csv.NewReader(file)
+       rc, err := rd.ReadAll()
+       return len(rc), err
+}
+func readCsvIsEmpty(fileName string) (string, error) {
+       file, err := os.Open(fileName)
+       if err != nil {
+               return "",err
+       }
+       defer file.Close()
+       var out string
+       var i, index int
+       var indexYear, indexStateAbbr, indexCityName, indexPopulationCount int
+
+       rd := bufio.NewReader(file)
+       for {
+               line, err := rd.ReadString('\n') // read a line
+               if io.EOF == err {
+                       break
+               }
+               if err != nil {
+                       return "",err
+               }
+
+               sptLint := strings.Split(line, ",")
+               if i == 0 {
+                       i = 1
+                       for _, val := range sptLint {
+                               switch val {
+                               case "Year":
+                                       indexYear = index
+                               case "StateAbbr":
+                                       indexStateAbbr = index
+                               case "CityName":
+                                       indexCityName = index
+                               case "PopulationCount":
+                                       indexPopulationCount = index
+                               }
+                               index++
+                       }
+               } else {
+                       if sptLint[indexCityName] != "" {
+                               outLine := sptLint[indexYear] + "," + sptLint[indexStateAbbr] + "," + sptLint[indexCityName] + "," + sptLint[indexPopulationCount] + "\n"
+                               out += outLine
+                       }
+               }
+       }
+
+       return out, nil
+}
+
+func readCsvLike(fileName string) (string, error) {
+       file, err := os.Open(fileName)
+       if err != nil {
+               return "",err
+       }
+       defer file.Close()
+       var out string
+       var i, index int
+       var indexYear,indexStateAbbr,indexCityName,indexPopulationCount,indexMeasure int
+
+       rd := bufio.NewReader(file)
+       for {
+               line, err := rd.ReadString('\n') // read a line
+               if io.EOF == err {
+                       break
+               }
+               if err != nil {
+                       return "",err
+               }
+
+               //utf8Lint := ConvertToString(line,"gbk", "utf-8")
+               sptLint := strings.Split(line[:(len(line)-1)], ",")
+               if i == 0 {
+                       i = 1
+                       for _, val := range sptLint {
+                               switch val {
+                               case "Year":indexYear = index
+                               case "StateAbbr":indexStateAbbr = index
+                               case "CityName":indexCityName = index
+                               case "Short_Question_Text":indexPopulationCount = index
+                               case "Measure":indexMeasure = index
+                               }
+                               index++
+                       }
+               } else {
+                       if sptLint[indexMeasure] != "" {
+                               reg := regexp.MustCompile("^.*blood pressure.*Years$")
+                               res := reg.FindAllString(sptLint[indexMeasure], -1)
+                               if len(res) > 0 {
+                                       outLine := sptLint[indexYear] + "," +sptLint[indexStateAbbr] + "," +sptLint[indexCityName] + "," + sptLint[indexPopulationCount] + "\n"
+                                       out += outLine
+                               }
+                       }
+               }
+       }
+
+       return out, nil
+}
+
+func readCsvRange(fileName string, l int, r int) (string, error) {
+       file, err := os.Open(fileName)
+       if err != nil {
+               return "", err
+       }
+       defer file.Close()
+       var out string
+       var i, index int
+       var indexYear,indexStateAbbr,indexCityName,indexPopulationCount int
+
+       rd := bufio.NewReader(file)
+       for j := 0; j < r + 1; j++ {
+               if j < l {
+                       continue
+               }
+               line, err := rd.ReadString('\n') // read a line
+               if io.EOF == err {
+                       break
+               }
+               if err != nil {
+                       return "", err
+               }
+
+               sptLint := strings.Split(line[:(len(line)-1)], ",")
+               if i == 0 {
+                       i = 1
+                       for _, val := range sptLint {
+                               switch val {
+                               case "Year":indexYear = index
+                               case "StateAbbr":indexStateAbbr = index
+                               case "CityName":indexCityName = index
+                               case "Short_Question_Text":indexPopulationCount = index
+                               }
+                               index++
+                       }
+               } else {
+                       outLine := sptLint[indexYear] + "," +sptLint[indexStateAbbr] + "," +sptLint[indexCityName] + "," + sptLint[indexPopulationCount] + "\n"
+                       out += outLine
+               }
+       }
+
+       return out, nil
+}
+
+func readCsvFloatAgg(fileName string) (avg, max, sum float64,  er error) {
+       file, err := os.Open(fileName)
+       if err != nil {
+               er = err
+               return
+       }
+       defer file.Close()
+       var i, index int
+       var indexDataValue int
+
+       rd := csv.NewReader(file)
+
+       for {
+               rc, err := rd.Read()
+               if io.EOF == err {
+                       break
+               }
+               if err != nil {
+                       er = err
+                       return
+               }
+               if i == 0 {
+                       i=1
+                       for index = 0; index < len(rc); index++ {
+                               if rc[index] == "Data_Value" {
+                                       indexDataValue = index
+                               }
+                       }
+               } else {
+                       if rc[indexDataValue] != "" {
+                               s1, err := strconv.ParseFloat(rc[indexDataValue], 64)
+                               if err != nil {
+                                       er = err
+                                       return
+                               }
+                               sum +=s1
+                               if s1 > max {
+                                       max = s1
+                               }
+                               i++
+                       }
+               }
+       }
+       avg = sum / float64(i-1)
+       return 
+}
+func readCsvConcat(fileName string) (string, error) {
+       var out string
+       file, err := os.Open(fileName)
+       if err != nil {
+               return out, err
+       }
+       defer file.Close()
+       var i int
+       var indexDataValue int
+       var indexYear,indexStateAbbr,indexCityName,indexShortQuestionText, indexDataValueUnit int
+       
+       rd := csv.NewReader(file)
+
+       for {
+               rc, err := rd.Read()
+               if io.EOF == err {
+                       break
+               }
+               if err != nil {
+                       return out, err
+               }
+               if i == 0 {
+                       for j, v := range rc {
+                               switch v {
+                                       case "Year":indexYear = j
+                                       case "StateAbbr":indexStateAbbr = j
+                                       case "CityName":indexCityName = j
+                                       case "Short_Question_Text":indexShortQuestionText = j
+                                       case "Data_Value_Unit":indexDataValueUnit = j
+                                       case "Data_Value":indexDataValue = j
+                               }
+                       }
+               } else {
+                       i++
+                       if rc[indexDataValue] != "" || rc[indexDataValueUnit] != "" {
+                               reg := regexp.MustCompile("^14.8.*$")
+                               reD := reg.FindAllString(rc[indexDataValue], -1)
+                               reDU := reg.FindAllString(rc[indexDataValueUnit], -1)
+                               if len(reD) > 0 || len(reDU) > 0 {
+                                       outLine := rc[indexYear] + "," +rc[indexStateAbbr] + "," +rc[indexCityName] + "," + rc[indexShortQuestionText] + "\n"
+                                       out += outLine
+                               }
+                       }
+               }
+               i++
+       }
+       return out, nil
+}
+func readCsvComplicateCondition(fileName string)(string, error) {
+       var out string
+       file, err := os.Open(fileName)
+       if err != nil {
+               return out, err
+       }
+       defer file.Close()
+       var i int
+       var indexDataValue, indexCategory, indexHighConfidenceLimit, indexMeasure int
+       var indexYear,indexStateAbbr,indexCityName,indexShortQuestionText, indexDataValueUnit int
+       
+       rd := csv.NewReader(file)
+
+       for {
+               rc, err := rd.Read()
+               if io.EOF == err {
+                       break
+               }
+               if err != nil {
+                       return out, err
+               }
+               if i == 0 {
+                       for j, v := range rc {
+                               switch v {
+                                       case "Year":indexYear = j
+                                       case "StateAbbr":indexStateAbbr = j
+                                       case "CityName":indexCityName = j
+                                       case "Short_Question_Text":indexShortQuestionText = j
+                                       case "Data_Value_Unit":indexDataValueUnit = j
+                                       case "Data_Value":indexDataValue = j
+                                       case "Measure":indexMeasure = j
+                                       case "Category":indexCategory = j
+                                       case "High_Confidence_Limit":indexHighConfidenceLimit = j
+                               }
+                       }
+               } else {
+                       reg := regexp.MustCompile("^.*18 Years$")
+                       reM := reg.FindAllString(rc[indexMeasure], -1)
+                       var dataV, limitV float64
+                       if rc[indexDataValue] != "" {
+                               dataV, err = strconv.ParseFloat(rc[indexDataValue], 64)
+                               if err != nil {
+                                       return out, err
+                               }
+                       }
+                       if rc[indexHighConfidenceLimit] != "" {
+                               limitV, err = strconv.ParseFloat(rc[indexHighConfidenceLimit], 64)
+                               if err != nil {
+                                       return out, err
+                               }
+                       }
+                       if dataV > 14.8 && rc[indexDataValueUnit] == "%" || len(reM) > 0  && 
+                               rc[indexCategory] == "Unhealthy Behaviors" || limitV > 70.0 {
+                                       outLine := rc[indexYear] + "," +rc[indexStateAbbr] + "," +rc[indexCityName] + "," + rc[indexShortQuestionText] + "," + rc[indexDataValue] + "," + rc[indexDataValueUnit] + "," + rc[indexCategory] + "," + rc[indexHighConfidenceLimit] + "\n"
+                                       out += outLine
+                       }
+               }
+               i++
+       }
+       return out, nil
+}
+
+
+type Extra struct {
+       Address string `json:"address"`
+       ContactForm string `json:"contact_form"`
+       Fax string `json:"fax,omitempty"`
+       How string `json:"how,omitempty"`
+       Office string `json:"office"`
+       RssUrl string `json:"rss_url,omitempty"`
+}
+
+type Person struct {
+       Bioguideid string `json:"bioguideid"`
+       Birthday string `json:"birthday"`
+       Cspanid int `json:"cspanid"`
+       Firstname string `json:"firstname"`
+       Gender string `json:"gender"`
+       GenderLabel string `json:"gender_label"`
+       Lastname string `json:"lastname"`
+       Link string `json:"link"`
+       Middlename string `json:"middlename"`
+       Name string `json:"name"`
+       Namemod string `json:"namemod"`
+       Nickname string `json:"nickname"`
+       Osid string `json:"osid"`
+       Pvsid *string `json:"pvsid"`
+       Sortname string `json:"sortname"`
+       Twitterid *string `json:"twitterid"`
+       Youtubeid *string `json:"youtubeid"`
+}
+
+type JsonLineSt struct {
+       Caucus *string `json:"caucus"`
+       CongressNumbers []int `json:"congress_numbers"`
+       Current  bool `json:"current"`
+       Description string `json:"description"`
+       District *string `json:"district"`
+       Enddate string `json:"enddate"`
+       Extra Extra `json:"extra"`
+       LeadershipTitle *string `json:"leadership_title"`
+       Party string `json:"party"`
+       Person Person `json:"person"`
+       Phone string `json:"phone"`
+       RoleType string `json:"role_type"`
+       RoleTypeLabel string `json:"role_type_label"`
+       SenatorClass string `json:"senator_class"`
+       SenatorClassLabel string `json:"senator_class_label"`
+       SenatorRank string `json:"senator_rank"`
+       SenatorRankLabel string `json:"senator_rank_label"`
+       Startdate string `json:"startdate"`
+       State string `json:"state"`
+       Title string `json:"title"`
+       TitleLong string `json:"title_long"`
+       Website string `json:"website"`
+}
+type Metast struct {
+       limit int 
+       Offset int
+       TotalCount int
+}
+
+type JsonSt struct {
+       Meta Metast
+       Objects []JsonLineSt `json:"objects"`
+}
+
+func readJsonDocument(fileName string) (string, error){
+       var out string
+       var data JsonSt
+    file, err := os.Open(fileName)
+    if err != nil {
+           return "",err
+    }
+    decoder := json.NewDecoder(file)
+    err = decoder.Decode(&data)
+    for _, v := range data.Objects {
+           if v.Party == "Democrat"{
+                  lint, err := json.Marshal(v)
+                  if err != nil {
+                          return "",err
+                  }
+                  lints := strings.Replace(string(lint), "\\u0026", "&", -1)
+                  out += lints + ","
+           }
+    }
+    return out, err
+}
+func readJsonLinesLike(fileName string) (string, error){
+       var out string
+       var data JsonSt
+       file, err := os.Open(fileName)
+       if err != nil {
+               return "",err
+       }
+       decoder := json.NewDecoder(file)
+       err = decoder.Decode(&data)
+       reg := regexp.MustCompile("^1959.*")
+       for _, v := range data.Objects {
+               reB := reg.FindAllString(v.Person.Birthday, -1)
+               if len(reB) > 0 {
+                       lints := "{\"firstname\":\"" + v.Person.Firstname + "\",\"lastname\":\"" + v.Person.Lastname + "\"}"
+                       out += lints + ","
+               }
+       }
+
+       return out, err
+}
+
+func readJsonLinesRange(fileName string, l, r int) (string, error){
+       var out string
+       var data JsonSt
+       var i int
+       file, err := os.Open(fileName)
+       if err != nil {
+               return "",err
+       }
+       decoder := json.NewDecoder(file)
+       err = decoder.Decode(&data)
+       for _, v := range data.Objects {
+               if i < l {
+                       continue
+               }
+               if i >= r {
+                       break
+               }
+               extrb, err := json.Marshal(v.Extra)
+               if err != nil {
+                       return "",err
+               }
+               extr := strings.Replace(string(extrb), "\\u0026", "&", -1)
+               
+               lints := "{\"firstname\":\"" + v.Person.Firstname + "\",\"lastname\":\"" + v.Person.Lastname +
+                       "\",\"extra\":" + extr + "}"
+               out += lints + ","
+               i++
+       }
+
+       return out, err
+}
+
+func readJsonFloatAggregation(fileName string) (float64, float64, float64, error){
+       var avg, max, min, sum float64
+       var data JsonSt
+       var i int
+       file, err := os.Open(fileName)
+       if err != nil {
+               return  avg, max, min, err
+       }
+       decoder := json.NewDecoder(file)
+       err = decoder.Decode(&data)
+       for _, v := range data.Objects {
+               if i == 0 {
+                       min = float64(v.Person.Cspanid)
+               }
+               if max < float64(v.Person.Cspanid) {
+                       max = float64(v.Person.Cspanid)
+               }
+               if min > float64(v.Person.Cspanid) {
+                       min = float64(v.Person.Cspanid)
+               }
+               sum += float64(v.Person.Cspanid)
+               i++
+       }
+       avg = sum / float64(i)
+       return avg, max, min, err
+}
+
+func readJsonDocumentConcat(fileName string) (string, error){
+       var out string
+       var data JsonSt
+       file, err := os.Open(fileName)
+       if err != nil {
+               return "",err
+       }
+       decoder := json.NewDecoder(file)
+       err = decoder.Decode(&data)
+
+       for _, v := range data.Objects {
+               if v.Person.Firstname + v.Person.Lastname == "JohnKennedy" {
+                       extrb, err := json.Marshal(v.Person)
+                       if err != nil {
+                               return "",err
+                       }
+                       extr := "{\"person\":" + strings.Replace(string(extrb), "\\u0026", "&", -1) + "}"
+                       out += extr + ","
+               }
+       }
+
+       return out, err
+}
+
+func readJsonComplicateConcat(fileName string) (string, error){
+       var out string
+       var data JsonSt
+       file, err := os.Open(fileName)
+       if err != nil {
+               return "",err
+       }
+       decoder := json.NewDecoder(file)
+       err = decoder.Decode(&data)
+
+       for _, v := range data.Objects {
+               if v.Startdate > "2017-01-01" && v.SenatorRank == "junior" || 
+                       v.State == "CA" && v.Party == "Repulican" {
+                       cn := "["
+                       for _,vv := range v.CongressNumbers {
+                               cn += strconv.Itoa(vv) + ","
+                       }
+                       cn = cn[:len(cn)-1] + "]"
+                       lints := "{\"firstname\":\"" + v.Person.Firstname + "\",\"lastname\":\"" + v.Person.Lastname + "\",\"congress_numbers\":" + cn + "}"
+                       out += lints + ","
+               }
+       }
+
+       return out, err
+}
\ No newline at end of file
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_object_type.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/select_object_type.go
new file mode 100644 (file)
index 0000000..8b75782
--- /dev/null
@@ -0,0 +1,364 @@
+package oss
+
+import (
+       "bytes"
+       "encoding/binary"
+       "fmt"
+       "hash"
+       "hash/crc32"
+       "io"
+       "net/http"
+       "time"
+)
+
+// The adapter class for Select object's response.
+// The response consists of frames. Each frame has the following format:
+
+// Type  |   Payload Length |  Header Checksum | Payload | Payload Checksum
+
+// |<4-->|  <--4 bytes------><---4 bytes-------><-n/a-----><--4 bytes--------->
+// And we have three kind of frames.
+// Data Frame:
+// Type:8388609
+// Payload:   Offset    |    Data
+//            <-8 bytes>
+
+// Continuous Frame
+// Type:8388612
+// Payload: Offset  (8-bytes)
+
+// End Frame
+// Type:8388613
+// Payload: Offset | total scanned bytes | http status code | error message
+//     <-- 8bytes--><-----8 bytes--------><---4 bytes-------><---variabe--->
+
+// SelectObjectResponse defines HTTP response from OSS SelectObject
+type SelectObjectResponse struct {
+       StatusCode          int
+       Headers             http.Header
+       Body                io.ReadCloser
+       Frame               SelectObjectResult
+       ReadTimeOut         uint
+       ClientCRC32         uint32
+       ServerCRC32         uint32
+       WriterForCheckCrc32 hash.Hash32
+       Finish              bool
+}
+
+func (sr *SelectObjectResponse) Read(p []byte) (n int, err error) {
+       n, err = sr.readFrames(p)
+       return
+}
+
+// Close http reponse body
+func (sr *SelectObjectResponse) Close() error {
+       return sr.Body.Close()
+}
+
+// PostSelectResult is the request of SelectObject
+type PostSelectResult struct {
+       Response *SelectObjectResponse
+}
+
+// readFrames is read Frame
+func (sr *SelectObjectResponse) readFrames(p []byte) (int, error) {
+       var nn int
+       var err error
+       var checkValid bool
+       if sr.Frame.OutputRawData == true {
+               nn, err = sr.Body.Read(p)
+               return nn, err
+       }
+
+       if sr.Finish {
+               return 0, io.EOF
+       }
+
+       for {
+               // if this Frame is Readed, then not reading Header
+               if sr.Frame.OpenLine != true {
+                       err = sr.analysisHeader()
+                       if err != nil {
+                               return nn, err
+                       }
+               }
+
+               if sr.Frame.FrameType == DataFrameType {
+                       n, err := sr.analysisData(p[nn:])
+                       if err != nil {
+                               return nn, err
+                       }
+                       nn += n
+
+                       // if this Frame is readed all data, then empty the Frame to read it with next frame
+                       if sr.Frame.ConsumedBytesLength == sr.Frame.PayloadLength-8 {
+                               checkValid, err = sr.checkPayloadSum()
+                               if err != nil || !checkValid {
+                                       return nn, fmt.Errorf("%s", err.Error())
+                               }
+                               sr.emptyFrame()
+                       }
+
+                       if nn == len(p) {
+                               return nn, nil
+                       }
+               } else if sr.Frame.FrameType == ContinuousFrameType {
+                       checkValid, err = sr.checkPayloadSum()
+                       if err != nil || !checkValid {
+                               return nn, fmt.Errorf("%s", err.Error())
+                       }
+               } else if sr.Frame.FrameType == EndFrameType {
+                       err = sr.analysisEndFrame()
+                       if err != nil {
+                               return nn, err
+                       }
+                       checkValid, err = sr.checkPayloadSum()
+                       if checkValid {
+                               sr.Finish = true
+                       }
+                       return nn, err
+               } else if sr.Frame.FrameType == MetaEndFrameCSVType {
+                       err = sr.analysisMetaEndFrameCSV()
+                       if err != nil {
+                               return nn, err
+                       }
+                       checkValid, err = sr.checkPayloadSum()
+                       if checkValid {
+                               sr.Finish = true
+                       }
+                       return nn, err
+               } else if sr.Frame.FrameType == MetaEndFrameJSONType {
+                       err = sr.analysisMetaEndFrameJSON()
+                       if err != nil {
+                               return nn, err
+                       }
+                       checkValid, err = sr.checkPayloadSum()
+                       if checkValid {
+                               sr.Finish = true
+                       }
+                       return nn, err
+               }
+       }
+       return nn, nil
+}
+
+type chanReadIO struct {
+       readLen int
+       err     error
+}
+
+func (sr *SelectObjectResponse) readLen(p []byte, timeOut time.Duration) (int, error) {
+       r := sr.Body
+       ch := make(chan chanReadIO, 1)
+       defer close(ch)
+       go func(p []byte) {
+               var needReadLength int
+               readChan := chanReadIO{}
+               needReadLength = len(p)
+               for {
+                       n, err := r.Read(p[readChan.readLen:needReadLength])
+                       readChan.readLen += n
+                       if err != nil {
+                               readChan.err = err
+                               ch <- readChan
+                               return
+                       }
+
+                       if readChan.readLen == needReadLength {
+                               break
+                       }
+               }
+               ch <- readChan
+       }(p)
+
+       select {
+       case <-time.After(time.Second * timeOut):
+               return 0, fmt.Errorf("requestId: %s, readLen timeout, timeout is %d(second),need read:%d", sr.Headers.Get(HTTPHeaderOssRequestID), timeOut, len(p))
+       case result := <-ch:
+               return result.readLen, result.err
+       }
+}
+
+// analysisHeader is reading selectObject response body's header
+func (sr *SelectObjectResponse) analysisHeader() error {
+       headFrameByte := make([]byte, 20)
+       _, err := sr.readLen(headFrameByte, time.Duration(sr.ReadTimeOut))
+       if err != nil {
+               return fmt.Errorf("requestId: %s, Read response frame header failure,err:%s", sr.Headers.Get(HTTPHeaderOssRequestID), err.Error())
+       }
+
+       frameTypeByte := headFrameByte[0:4]
+       sr.Frame.Version = frameTypeByte[0]
+       frameTypeByte[0] = 0
+       bytesToInt(frameTypeByte, &sr.Frame.FrameType)
+
+       if sr.Frame.FrameType != DataFrameType && sr.Frame.FrameType != ContinuousFrameType &&
+               sr.Frame.FrameType != EndFrameType && sr.Frame.FrameType != MetaEndFrameCSVType && sr.Frame.FrameType != MetaEndFrameJSONType {
+               return fmt.Errorf("requestId: %s, Unexpected frame type: %d", sr.Headers.Get(HTTPHeaderOssRequestID), sr.Frame.FrameType)
+       }
+
+       payloadLengthByte := headFrameByte[4:8]
+       bytesToInt(payloadLengthByte, &sr.Frame.PayloadLength)
+       headCheckSumByte := headFrameByte[8:12]
+       bytesToInt(headCheckSumByte, &sr.Frame.HeaderCheckSum)
+       byteOffset := headFrameByte[12:20]
+       bytesToInt(byteOffset, &sr.Frame.Offset)
+       sr.Frame.OpenLine = true
+
+       err = sr.writerCheckCrc32(byteOffset)
+       return err
+}
+
+// analysisData is reading the DataFrameType data of selectObject response body
+func (sr *SelectObjectResponse) analysisData(p []byte) (int, error) {
+       var needReadLength int32
+       lenP := int32(len(p))
+       restByteLength := sr.Frame.PayloadLength - 8 - sr.Frame.ConsumedBytesLength
+       if lenP <= restByteLength {
+               needReadLength = lenP
+       } else {
+               needReadLength = restByteLength
+       }
+       n, err := sr.readLen(p[:needReadLength], time.Duration(sr.ReadTimeOut))
+       if err != nil {
+               return n, fmt.Errorf("read frame data error,%s", err.Error())
+       }
+       sr.Frame.ConsumedBytesLength += int32(n)
+       err = sr.writerCheckCrc32(p[:n])
+       return n, err
+}
+
+// analysisEndFrame is reading the EndFrameType data of selectObject response body
+func (sr *SelectObjectResponse) analysisEndFrame() error {
+       var eF EndFrame
+       payLoadBytes := make([]byte, sr.Frame.PayloadLength-8)
+       _, err := sr.readLen(payLoadBytes, time.Duration(sr.ReadTimeOut))
+       if err != nil {
+               return fmt.Errorf("read end frame error:%s", err.Error())
+       }
+       bytesToInt(payLoadBytes[0:8], &eF.TotalScanned)
+       bytesToInt(payLoadBytes[8:12], &eF.HTTPStatusCode)
+       errMsgLength := sr.Frame.PayloadLength - 20
+       eF.ErrorMsg = string(payLoadBytes[12 : errMsgLength+12])
+       sr.Frame.EndFrame.TotalScanned = eF.TotalScanned
+       sr.Frame.EndFrame.HTTPStatusCode = eF.HTTPStatusCode
+       sr.Frame.EndFrame.ErrorMsg = eF.ErrorMsg
+       err = sr.writerCheckCrc32(payLoadBytes)
+       return err
+}
+
+// analysisMetaEndFrameCSV is reading the MetaEndFrameCSVType data of selectObject response body
+func (sr *SelectObjectResponse) analysisMetaEndFrameCSV() error {
+       var mCF MetaEndFrameCSV
+       payLoadBytes := make([]byte, sr.Frame.PayloadLength-8)
+       _, err := sr.readLen(payLoadBytes, time.Duration(sr.ReadTimeOut))
+       if err != nil {
+               return fmt.Errorf("read meta end csv frame error:%s", err.Error())
+       }
+
+       bytesToInt(payLoadBytes[0:8], &mCF.TotalScanned)
+       bytesToInt(payLoadBytes[8:12], &mCF.Status)
+       bytesToInt(payLoadBytes[12:16], &mCF.SplitsCount)
+       bytesToInt(payLoadBytes[16:24], &mCF.RowsCount)
+       bytesToInt(payLoadBytes[24:28], &mCF.ColumnsCount)
+       errMsgLength := sr.Frame.PayloadLength - 36
+       mCF.ErrorMsg = string(payLoadBytes[28 : errMsgLength+28])
+       sr.Frame.MetaEndFrameCSV.ErrorMsg = mCF.ErrorMsg
+       sr.Frame.MetaEndFrameCSV.TotalScanned = mCF.TotalScanned
+       sr.Frame.MetaEndFrameCSV.Status = mCF.Status
+       sr.Frame.MetaEndFrameCSV.SplitsCount = mCF.SplitsCount
+       sr.Frame.MetaEndFrameCSV.RowsCount = mCF.RowsCount
+       sr.Frame.MetaEndFrameCSV.ColumnsCount = mCF.ColumnsCount
+       err = sr.writerCheckCrc32(payLoadBytes)
+       return err
+}
+
+// analysisMetaEndFrameJSON is reading the MetaEndFrameJSONType data of selectObject response body
+func (sr *SelectObjectResponse) analysisMetaEndFrameJSON() error {
+       var mJF MetaEndFrameJSON
+       payLoadBytes := make([]byte, sr.Frame.PayloadLength-8)
+       _, err := sr.readLen(payLoadBytes, time.Duration(sr.ReadTimeOut))
+       if err != nil {
+               return fmt.Errorf("read meta end json frame error:%s", err.Error())
+       }
+
+       bytesToInt(payLoadBytes[0:8], &mJF.TotalScanned)
+       bytesToInt(payLoadBytes[8:12], &mJF.Status)
+       bytesToInt(payLoadBytes[12:16], &mJF.SplitsCount)
+       bytesToInt(payLoadBytes[16:24], &mJF.RowsCount)
+       errMsgLength := sr.Frame.PayloadLength - 32
+       mJF.ErrorMsg = string(payLoadBytes[24 : errMsgLength+24])
+       sr.Frame.MetaEndFrameJSON.ErrorMsg = mJF.ErrorMsg
+       sr.Frame.MetaEndFrameJSON.TotalScanned = mJF.TotalScanned
+       sr.Frame.MetaEndFrameJSON.Status = mJF.Status
+       sr.Frame.MetaEndFrameJSON.SplitsCount = mJF.SplitsCount
+       sr.Frame.MetaEndFrameJSON.RowsCount = mJF.RowsCount
+
+       err = sr.writerCheckCrc32(payLoadBytes)
+       return err
+}
+
+func (sr *SelectObjectResponse) checkPayloadSum() (bool, error) {
+       payLoadChecksumByte := make([]byte, 4)
+       n, err := sr.readLen(payLoadChecksumByte, time.Duration(sr.ReadTimeOut))
+       if n == 4 {
+               bytesToInt(payLoadChecksumByte, &sr.Frame.PayloadChecksum)
+               sr.ServerCRC32 = sr.Frame.PayloadChecksum
+               sr.ClientCRC32 = sr.WriterForCheckCrc32.Sum32()
+               if sr.Frame.EnablePayloadCrc == true && sr.ServerCRC32 != 0 && sr.ServerCRC32 != sr.ClientCRC32 {
+                       return false, fmt.Errorf("RequestId: %s, Unexpected frame type: %d, client %d but server %d",
+                               sr.Headers.Get(HTTPHeaderOssRequestID), sr.Frame.FrameType, sr.ClientCRC32, sr.ServerCRC32)
+               }
+               return true, err
+       }
+       return false, fmt.Errorf("RequestId:%s, read checksum error:%s", sr.Headers.Get(HTTPHeaderOssRequestID), err.Error())
+}
+
+func (sr *SelectObjectResponse) writerCheckCrc32(p []byte) (err error) {
+       err = nil
+       if sr.Frame.EnablePayloadCrc == true {
+               _, err = sr.WriterForCheckCrc32.Write(p)
+       }
+       return err
+}
+
+// emptyFrame is emptying SelectObjectResponse Frame information
+func (sr *SelectObjectResponse) emptyFrame() {
+       crcCalc := crc32.NewIEEE()
+       sr.WriterForCheckCrc32 = crcCalc
+       sr.Finish = false
+
+       sr.Frame.ConsumedBytesLength = 0
+       sr.Frame.OpenLine = false
+       sr.Frame.Version = byte(0)
+       sr.Frame.FrameType = 0
+       sr.Frame.PayloadLength = 0
+       sr.Frame.HeaderCheckSum = 0
+       sr.Frame.Offset = 0
+       sr.Frame.Data = ""
+
+       sr.Frame.EndFrame.TotalScanned = 0
+       sr.Frame.EndFrame.HTTPStatusCode = 0
+       sr.Frame.EndFrame.ErrorMsg = ""
+
+       sr.Frame.MetaEndFrameCSV.TotalScanned = 0
+       sr.Frame.MetaEndFrameCSV.Status = 0
+       sr.Frame.MetaEndFrameCSV.SplitsCount = 0
+       sr.Frame.MetaEndFrameCSV.RowsCount = 0
+       sr.Frame.MetaEndFrameCSV.ColumnsCount = 0
+       sr.Frame.MetaEndFrameCSV.ErrorMsg = ""
+
+       sr.Frame.MetaEndFrameJSON.TotalScanned = 0
+       sr.Frame.MetaEndFrameJSON.Status = 0
+       sr.Frame.MetaEndFrameJSON.SplitsCount = 0
+       sr.Frame.MetaEndFrameJSON.RowsCount = 0
+       sr.Frame.MetaEndFrameJSON.ErrorMsg = ""
+
+       sr.Frame.PayloadChecksum = 0
+}
+
+// bytesToInt byte's array trans to int
+func bytesToInt(b []byte, ret interface{}) {
+       binBuf := bytes.NewBuffer(b)
+       binary.Read(binBuf, binary.BigEndian, ret)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_6.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_6.go
new file mode 100644 (file)
index 0000000..795ca8b
--- /dev/null
@@ -0,0 +1,34 @@
+// +build !go1.7
+
+package oss
+
+import (
+       "net"
+       "net/http"
+       "time"
+)
+
+func newTransport(conn *Conn, config *Config) *http.Transport {
+       httpTimeOut := conn.config.HTTPTimeout
+       httpMaxConns := conn.config.HTTPMaxConns
+       // New Transport
+       transport := &http.Transport{
+               Dial: func(netw, addr string) (net.Conn, error) {
+                       d := net.Dialer{
+                               Timeout:   httpTimeOut.ConnectTimeout,
+                               KeepAlive: 30 * time.Second,
+                       }
+                       if config.LocalAddr != nil {
+                               d.LocalAddr = config.LocalAddr
+                       }
+                       conn, err := d.Dial(netw, addr)
+                       if err != nil {
+                               return nil, err
+                       }
+                       return newTimeoutConn(conn, httpTimeOut.ReadWriteTimeout, httpTimeOut.LongTimeout), nil
+               },
+               MaxIdleConnsPerHost:   httpMaxConns.MaxIdleConnsPerHost,
+               ResponseHeaderTimeout: httpTimeOut.HeaderTimeout,
+       }
+       return transport
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_7.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_7.go
new file mode 100644 (file)
index 0000000..757543b
--- /dev/null
@@ -0,0 +1,36 @@
+// +build go1.7
+
+package oss
+
+import (
+       "net"
+       "net/http"
+       "time"
+)
+
+func newTransport(conn *Conn, config *Config) *http.Transport {
+       httpTimeOut := conn.config.HTTPTimeout
+       httpMaxConns := conn.config.HTTPMaxConns
+       // New Transport
+       transport := &http.Transport{
+               Dial: func(netw, addr string) (net.Conn, error) {
+                       d := net.Dialer{
+                               Timeout:   httpTimeOut.ConnectTimeout,
+                               KeepAlive: 30 * time.Second,
+                       }
+                       if config.LocalAddr != nil {
+                               d.LocalAddr = config.LocalAddr
+                       }
+                       conn, err := d.Dial(netw, addr)
+                       if err != nil {
+                               return nil, err
+                       }
+                       return newTimeoutConn(conn, httpTimeOut.ReadWriteTimeout, httpTimeOut.LongTimeout), nil
+               },
+               MaxIdleConns:          httpMaxConns.MaxIdleConns,
+               MaxIdleConnsPerHost:   httpMaxConns.MaxIdleConnsPerHost,
+               IdleConnTimeout:       httpTimeOut.IdleConnTimeout,
+               ResponseHeaderTimeout: httpTimeOut.HeaderTimeout,
+       }
+       return transport
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type.go
new file mode 100644 (file)
index 0000000..718991e
--- /dev/null
@@ -0,0 +1,1247 @@
+package oss
+
+import (
+       "encoding/base64"
+       "encoding/xml"
+       "fmt"
+       "net/url"
+       "time"
+)
+
+// ListBucketsResult defines the result object from ListBuckets request
+type ListBucketsResult struct {
+       XMLName     xml.Name           `xml:"ListAllMyBucketsResult"`
+       Prefix      string             `xml:"Prefix"`         // The prefix in this query
+       Marker      string             `xml:"Marker"`         // The marker filter
+       MaxKeys     int                `xml:"MaxKeys"`        // The max entry count to return. This information is returned when IsTruncated is true.
+       IsTruncated bool               `xml:"IsTruncated"`    // Flag true means there's remaining buckets to return.
+       NextMarker  string             `xml:"NextMarker"`     // The marker filter for the next list call
+       Owner       Owner              `xml:"Owner"`          // The owner information
+       Buckets     []BucketProperties `xml:"Buckets>Bucket"` // The bucket list
+}
+
+// BucketProperties defines bucket properties
+type BucketProperties struct {
+       XMLName      xml.Name  `xml:"Bucket"`
+       Name         string    `xml:"Name"`         // Bucket name
+       Location     string    `xml:"Location"`     // Bucket datacenter
+       CreationDate time.Time `xml:"CreationDate"` // Bucket create time
+       StorageClass string    `xml:"StorageClass"` // Bucket storage class
+}
+
+// GetBucketACLResult defines GetBucketACL request's result
+type GetBucketACLResult struct {
+       XMLName xml.Name `xml:"AccessControlPolicy"`
+       ACL     string   `xml:"AccessControlList>Grant"` // Bucket ACL
+       Owner   Owner    `xml:"Owner"`                   // Bucket owner
+}
+
+// LifecycleConfiguration is the Bucket Lifecycle configuration
+type LifecycleConfiguration struct {
+       XMLName xml.Name        `xml:"LifecycleConfiguration"`
+       Rules   []LifecycleRule `xml:"Rule"`
+}
+
+// LifecycleRule defines Lifecycle rules
+type LifecycleRule struct {
+       XMLName              xml.Name                       `xml:"Rule"`
+       ID                   string                         `xml:"ID,omitempty"`                   // The rule ID
+       Prefix               string                         `xml:"Prefix"`                         // The object key prefix
+       Status               string                         `xml:"Status"`                         // The rule status (enabled or not)
+       Tags                 []Tag                          `xml:"Tag,omitempty"`                  // the tags property
+       Expiration           *LifecycleExpiration           `xml:"Expiration,omitempty"`           // The expiration property
+       Transitions          []LifecycleTransition          `xml:"Transition,omitempty"`           // The transition property
+       AbortMultipartUpload *LifecycleAbortMultipartUpload `xml:"AbortMultipartUpload,omitempty"` // The AbortMultipartUpload property
+       NonVersionExpiration *LifecycleVersionExpiration    `xml:"NoncurrentVersionExpiration,omitempty"`
+       // Deprecated: Use NonVersionTransitions instead.
+       NonVersionTransition  *LifecycleVersionTransition  `xml:"-"` // NonVersionTransition is not suggested to use
+       NonVersionTransitions []LifecycleVersionTransition `xml:"NoncurrentVersionTransition,omitempty"`
+}
+
+// LifecycleExpiration defines the rule's expiration property
+type LifecycleExpiration struct {
+       XMLName                   xml.Name `xml:"Expiration"`
+       Days                      int      `xml:"Days,omitempty"`                      // Relative expiration time: The expiration time in days after the last modified time
+       Date                      string   `xml:"Date,omitempty"`                      // Absolute expiration time: The expiration time in date, not recommended
+       CreatedBeforeDate         string   `xml:"CreatedBeforeDate,omitempty"`         // objects created before the date will be expired
+       ExpiredObjectDeleteMarker *bool    `xml:"ExpiredObjectDeleteMarker,omitempty"` // Specifies whether the expired delete tag is automatically deleted
+}
+
+// LifecycleTransition defines the rule's transition propery
+type LifecycleTransition struct {
+       XMLName           xml.Name         `xml:"Transition"`
+       Days              int              `xml:"Days,omitempty"`              // Relative transition time: The transition time in days after the last modified time
+       CreatedBeforeDate string           `xml:"CreatedBeforeDate,omitempty"` // objects created before the date will be expired
+       StorageClass      StorageClassType `xml:"StorageClass,omitempty"`      // Specifies the target storage type
+}
+
+// LifecycleAbortMultipartUpload defines the rule's abort multipart upload propery
+type LifecycleAbortMultipartUpload struct {
+       XMLName           xml.Name `xml:"AbortMultipartUpload"`
+       Days              int      `xml:"Days,omitempty"`              // Relative expiration time: The expiration time in days after the last modified time
+       CreatedBeforeDate string   `xml:"CreatedBeforeDate,omitempty"` // objects created before the date will be expired
+}
+
+// LifecycleVersionExpiration defines the rule's NoncurrentVersionExpiration propery
+type LifecycleVersionExpiration struct {
+       XMLName        xml.Name `xml:"NoncurrentVersionExpiration"`
+       NoncurrentDays int      `xml:"NoncurrentDays,omitempty"` // How many days after the Object becomes a non-current version
+}
+
+// LifecycleVersionTransition defines the rule's NoncurrentVersionTransition propery
+type LifecycleVersionTransition struct {
+       XMLName        xml.Name         `xml:"NoncurrentVersionTransition"`
+       NoncurrentDays int              `xml:"NoncurrentDays,omitempty"` // How many days after the Object becomes a non-current version
+       StorageClass   StorageClassType `xml:"StorageClass,omitempty"`
+}
+
+const iso8601DateFormat = "2006-01-02T15:04:05.000Z"
+
+// BuildLifecycleRuleByDays builds a lifecycle rule objects will expiration in days after the last modified time
+func BuildLifecycleRuleByDays(id, prefix string, status bool, days int) LifecycleRule {
+       var statusStr = "Enabled"
+       if !status {
+               statusStr = "Disabled"
+       }
+       return LifecycleRule{ID: id, Prefix: prefix, Status: statusStr,
+               Expiration: &LifecycleExpiration{Days: days}}
+}
+
+// BuildLifecycleRuleByDate builds a lifecycle rule objects will expiration in specified date
+func BuildLifecycleRuleByDate(id, prefix string, status bool, year, month, day int) LifecycleRule {
+       var statusStr = "Enabled"
+       if !status {
+               statusStr = "Disabled"
+       }
+       date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC).Format(iso8601DateFormat)
+       return LifecycleRule{ID: id, Prefix: prefix, Status: statusStr,
+               Expiration: &LifecycleExpiration{Date: date}}
+}
+
+// ValidateLifecycleRule Determine if a lifecycle rule is valid, if it is invalid, it will return an error.
+func verifyLifecycleRules(rules []LifecycleRule) error {
+       if len(rules) == 0 {
+               return fmt.Errorf("invalid rules, the length of rules is zero")
+       }
+       for k, rule := range rules {
+               if rule.Status != "Enabled" && rule.Status != "Disabled" {
+                       return fmt.Errorf("invalid rule, the value of status must be Enabled or Disabled")
+               }
+
+               abortMPU := rule.AbortMultipartUpload
+               if abortMPU != nil {
+                       if (abortMPU.Days != 0 && abortMPU.CreatedBeforeDate != "") || (abortMPU.Days == 0 && abortMPU.CreatedBeforeDate == "") {
+                               return fmt.Errorf("invalid abort multipart upload lifecycle, must be set one of CreatedBeforeDate and Days")
+                       }
+               }
+
+               transitions := rule.Transitions
+               if len(transitions) > 0 {
+                       for _, transition := range transitions {
+                               if (transition.Days != 0 && transition.CreatedBeforeDate != "") || (transition.Days == 0 && transition.CreatedBeforeDate == "") {
+                                       return fmt.Errorf("invalid transition lifecycle, must be set one of CreatedBeforeDate and Days")
+                               }
+                       }
+               }
+
+               // NonVersionTransition is not suggested to use
+               // to keep compatible
+               if rule.NonVersionTransition != nil && len(rule.NonVersionTransitions) > 0 {
+                       return fmt.Errorf("NonVersionTransition and NonVersionTransitions cannot both have values")
+               } else if rule.NonVersionTransition != nil {
+                       rules[k].NonVersionTransitions = append(rules[k].NonVersionTransitions, *rule.NonVersionTransition)
+               }
+       }
+
+       return nil
+}
+
+// GetBucketLifecycleResult defines GetBucketLifecycle's result object
+type GetBucketLifecycleResult LifecycleConfiguration
+
+// RefererXML defines Referer configuration
+type RefererXML struct {
+       XMLName           xml.Name `xml:"RefererConfiguration"`
+       AllowEmptyReferer bool     `xml:"AllowEmptyReferer"`   // Allow empty referrer
+       RefererList       []string `xml:"RefererList>Referer"` // Referer whitelist
+}
+
+// GetBucketRefererResult defines result object for GetBucketReferer request
+type GetBucketRefererResult RefererXML
+
+// LoggingXML defines logging configuration
+type LoggingXML struct {
+       XMLName        xml.Name       `xml:"BucketLoggingStatus"`
+       LoggingEnabled LoggingEnabled `xml:"LoggingEnabled"` // The logging configuration information
+}
+
+type loggingXMLEmpty struct {
+       XMLName xml.Name `xml:"BucketLoggingStatus"`
+}
+
+// LoggingEnabled defines the logging configuration information
+type LoggingEnabled struct {
+       XMLName      xml.Name `xml:"LoggingEnabled"`
+       TargetBucket string   `xml:"TargetBucket"` // The bucket name for storing the log files
+       TargetPrefix string   `xml:"TargetPrefix"` // The log file prefix
+}
+
+// GetBucketLoggingResult defines the result from GetBucketLogging request
+type GetBucketLoggingResult LoggingXML
+
+// WebsiteXML defines Website configuration
+type WebsiteXML struct {
+       XMLName       xml.Name      `xml:"WebsiteConfiguration"`
+       IndexDocument IndexDocument `xml:"IndexDocument,omitempty"`            // The index page
+       ErrorDocument ErrorDocument `xml:"ErrorDocument,omitempty"`            // The error page
+       RoutingRules  []RoutingRule `xml:"RoutingRules>RoutingRule,omitempty"` // The routing Rule list
+}
+
+// IndexDocument defines the index page info
+type IndexDocument struct {
+       XMLName xml.Name `xml:"IndexDocument"`
+       Suffix  string   `xml:"Suffix"` // The file name for the index page
+}
+
+// ErrorDocument defines the 404 error page info
+type ErrorDocument struct {
+       XMLName xml.Name `xml:"ErrorDocument"`
+       Key     string   `xml:"Key"` // 404 error file name
+}
+
+// RoutingRule defines the routing rules
+type RoutingRule struct {
+       XMLName    xml.Name  `xml:"RoutingRule"`
+       RuleNumber int       `xml:"RuleNumber,omitempty"` // The routing number
+       Condition  Condition `xml:"Condition,omitempty"`  // The routing condition
+       Redirect   Redirect  `xml:"Redirect,omitempty"`   // The routing redirect
+
+}
+
+// Condition defines codition in the RoutingRule
+type Condition struct {
+       XMLName                     xml.Name        `xml:"Condition"`
+       KeyPrefixEquals             string          `xml:"KeyPrefixEquals,omitempty"`             // Matching objcet prefix
+       HTTPErrorCodeReturnedEquals int             `xml:"HttpErrorCodeReturnedEquals,omitempty"` // The rule is for Accessing to the specified object
+       IncludeHeader               []IncludeHeader `xml:"IncludeHeader"`                         // The rule is for request which include header
+}
+
+// IncludeHeader defines includeHeader in the RoutingRule's Condition
+type IncludeHeader struct {
+       XMLName xml.Name `xml:"IncludeHeader"`
+       Key     string   `xml:"Key,omitempty"`    // The Include header key
+       Equals  string   `xml:"Equals,omitempty"` // The Include header value
+}
+
+// Redirect defines redirect in the RoutingRule
+type Redirect struct {
+       XMLName               xml.Name      `xml:"Redirect"`
+       RedirectType          string        `xml:"RedirectType,omitempty"`         // The redirect type, it have Mirror,External,Internal,AliCDN
+       PassQueryString       *bool         `xml:"PassQueryString"`                // Whether to send the specified request's parameters, true or false
+       MirrorURL             string        `xml:"MirrorURL,omitempty"`            // Mirror of the website address back to the source.
+       MirrorPassQueryString *bool         `xml:"MirrorPassQueryString"`          // To Mirror of the website Whether to send the specified request's parameters, true or false
+       MirrorFollowRedirect  *bool         `xml:"MirrorFollowRedirect"`           // Redirect the location, if the mirror return 3XX
+       MirrorCheckMd5        *bool         `xml:"MirrorCheckMd5"`                 // Check the mirror is MD5.
+       MirrorHeaders         MirrorHeaders `xml:"MirrorHeaders,omitempty"`        // Mirror headers
+       Protocol              string        `xml:"Protocol,omitempty"`             // The redirect Protocol
+       HostName              string        `xml:"HostName,omitempty"`             // The redirect HostName
+       ReplaceKeyPrefixWith  string        `xml:"ReplaceKeyPrefixWith,omitempty"` // object name'Prefix replace the value
+       HttpRedirectCode      int           `xml:"HttpRedirectCode,omitempty"`     // THe redirect http code
+       ReplaceKeyWith        string        `xml:"ReplaceKeyWith,omitempty"`       // object name replace the value
+}
+
+// MirrorHeaders defines MirrorHeaders in the Redirect
+type MirrorHeaders struct {
+       XMLName xml.Name          `xml:"MirrorHeaders"`
+       PassAll *bool             `xml:"PassAll"` // Penetrating all of headers to source website.
+       Pass    []string          `xml:"Pass"`    // Penetrating some of headers to source website.
+       Remove  []string          `xml:"Remove"`  // Prohibit passthrough some of headers to source website
+       Set     []MirrorHeaderSet `xml:"Set"`     // Setting some of headers send to source website
+}
+
+// MirrorHeaderSet defines Set for Redirect's MirrorHeaders
+type MirrorHeaderSet struct {
+       XMLName xml.Name `xml:"Set"`
+       Key     string   `xml:"Key,omitempty"`   // The mirror header key
+       Value   string   `xml:"Value,omitempty"` // The mirror header value
+}
+
+// GetBucketWebsiteResult defines the result from GetBucketWebsite request.
+type GetBucketWebsiteResult WebsiteXML
+
+// CORSXML defines CORS configuration
+type CORSXML struct {
+       XMLName   xml.Name   `xml:"CORSConfiguration"`
+       CORSRules []CORSRule `xml:"CORSRule"` // CORS rules
+}
+
+// CORSRule defines CORS rules
+type CORSRule struct {
+       XMLName       xml.Name `xml:"CORSRule"`
+       AllowedOrigin []string `xml:"AllowedOrigin"` // Allowed origins. By default it's wildcard '*'
+       AllowedMethod []string `xml:"AllowedMethod"` // Allowed methods
+       AllowedHeader []string `xml:"AllowedHeader"` // Allowed headers
+       ExposeHeader  []string `xml:"ExposeHeader"`  // Allowed response headers
+       MaxAgeSeconds int      `xml:"MaxAgeSeconds"` // Max cache ages in seconds
+}
+
+// GetBucketCORSResult defines the result from GetBucketCORS request.
+type GetBucketCORSResult CORSXML
+
+// GetBucketInfoResult defines the result from GetBucketInfo request.
+type GetBucketInfoResult struct {
+       XMLName    xml.Name   `xml:"BucketInfo"`
+       BucketInfo BucketInfo `xml:"Bucket"`
+}
+
+// BucketInfo defines Bucket information
+type BucketInfo struct {
+       XMLName          xml.Name  `xml:"Bucket"`
+       Name             string    `xml:"Name"`                     // Bucket name
+       Location         string    `xml:"Location"`                 // Bucket datacenter
+       CreationDate     time.Time `xml:"CreationDate"`             // Bucket creation time
+       ExtranetEndpoint string    `xml:"ExtranetEndpoint"`         // Bucket external endpoint
+       IntranetEndpoint string    `xml:"IntranetEndpoint"`         // Bucket internal endpoint
+       ACL              string    `xml:"AccessControlList>Grant"`  // Bucket ACL
+       RedundancyType   string    `xml:"DataRedundancyType"`       // Bucket DataRedundancyType
+       Owner            Owner     `xml:"Owner"`                    // Bucket owner
+       StorageClass     string    `xml:"StorageClass"`             // Bucket storage class
+       SseRule          SSERule   `xml:"ServerSideEncryptionRule"` // Bucket ServerSideEncryptionRule
+       Versioning       string    `xml:"Versioning"`               // Bucket Versioning
+}
+
+type SSERule struct {
+       XMLName           xml.Name `xml:"ServerSideEncryptionRule"`    // Bucket ServerSideEncryptionRule
+       KMSMasterKeyID    string   `xml:"KMSMasterKeyID,omitempty"`    // Bucket KMSMasterKeyID
+       SSEAlgorithm      string   `xml:"SSEAlgorithm,omitempty"`      // Bucket SSEAlgorithm
+       KMSDataEncryption string   `xml:"KMSDataEncryption,omitempty"` //Bucket KMSDataEncryption
+}
+
+// ListObjectsResult defines the result from ListObjects request
+type ListObjectsResult struct {
+       XMLName        xml.Name           `xml:"ListBucketResult"`
+       Prefix         string             `xml:"Prefix"`                // The object prefix
+       Marker         string             `xml:"Marker"`                // The marker filter.
+       MaxKeys        int                `xml:"MaxKeys"`               // Max keys to return
+       Delimiter      string             `xml:"Delimiter"`             // The delimiter for grouping objects' name
+       IsTruncated    bool               `xml:"IsTruncated"`           // Flag indicates if all results are returned (when it's false)
+       NextMarker     string             `xml:"NextMarker"`            // The start point of the next query
+       Objects        []ObjectProperties `xml:"Contents"`              // Object list
+       CommonPrefixes []string           `xml:"CommonPrefixes>Prefix"` // You can think of commonprefixes as "folders" whose names end with the delimiter
+}
+
+// ObjectProperties defines Objecct properties
+type ObjectProperties struct {
+       XMLName      xml.Name  `xml:"Contents"`
+       Key          string    `xml:"Key"`          // Object key
+       Type         string    `xml:"Type"`         // Object type
+       Size         int64     `xml:"Size"`         // Object size
+       ETag         string    `xml:"ETag"`         // Object ETag
+       Owner        Owner     `xml:"Owner"`        // Object owner information
+       LastModified time.Time `xml:"LastModified"` // Object last modified time
+       StorageClass string    `xml:"StorageClass"` // Object storage class (Standard, IA, Archive)
+}
+
+// ListObjectsResultV2 defines the result from ListObjectsV2 request
+type ListObjectsResultV2 struct {
+       XMLName               xml.Name           `xml:"ListBucketResult"`
+       Prefix                string             `xml:"Prefix"`                // The object prefix
+       StartAfter            string             `xml:"StartAfter"`            // the input StartAfter
+       ContinuationToken     string             `xml:"ContinuationToken"`     // the input ContinuationToken
+       MaxKeys               int                `xml:"MaxKeys"`               // Max keys to return
+       Delimiter             string             `xml:"Delimiter"`             // The delimiter for grouping objects' name
+       IsTruncated           bool               `xml:"IsTruncated"`           // Flag indicates if all results are returned (when it's false)
+       NextContinuationToken string             `xml:"NextContinuationToken"` // The start point of the next NextContinuationToken
+       Objects               []ObjectProperties `xml:"Contents"`              // Object list
+       CommonPrefixes        []string           `xml:"CommonPrefixes>Prefix"` // You can think of commonprefixes as "folders" whose names end with the delimiter
+}
+
+// ListObjectVersionsResult defines the result from ListObjectVersions request
+type ListObjectVersionsResult struct {
+       XMLName             xml.Name                       `xml:"ListVersionsResult"`
+       Name                string                         `xml:"Name"`                  // The Bucket Name
+       Owner               Owner                          `xml:"Owner"`                 // The owner of bucket
+       Prefix              string                         `xml:"Prefix"`                // The object prefix
+       KeyMarker           string                         `xml:"KeyMarker"`             // The start marker filter.
+       VersionIdMarker     string                         `xml:"VersionIdMarker"`       // The start VersionIdMarker filter.
+       MaxKeys             int                            `xml:"MaxKeys"`               // Max keys to return
+       Delimiter           string                         `xml:"Delimiter"`             // The delimiter for grouping objects' name
+       IsTruncated         bool                           `xml:"IsTruncated"`           // Flag indicates if all results are returned (when it's false)
+       NextKeyMarker       string                         `xml:"NextKeyMarker"`         // The start point of the next query
+       NextVersionIdMarker string                         `xml:"NextVersionIdMarker"`   // The start point of the next query
+       CommonPrefixes      []string                       `xml:"CommonPrefixes>Prefix"` // You can think of commonprefixes as "folders" whose names end with the delimiter
+       ObjectDeleteMarkers []ObjectDeleteMarkerProperties `xml:"DeleteMarker"`          // DeleteMarker list
+       ObjectVersions      []ObjectVersionProperties      `xml:"Version"`               // version list
+}
+
+type ObjectDeleteMarkerProperties struct {
+       XMLName      xml.Name  `xml:"DeleteMarker"`
+       Key          string    `xml:"Key"`          // The Object Key
+       VersionId    string    `xml:"VersionId"`    // The Object VersionId
+       IsLatest     bool      `xml:"IsLatest"`     // is current version or not
+       LastModified time.Time `xml:"LastModified"` // Object last modified time
+       Owner        Owner     `xml:"Owner"`        // bucket owner element
+}
+
+type ObjectVersionProperties struct {
+       XMLName      xml.Name  `xml:"Version"`
+       Key          string    `xml:"Key"`          // The Object Key
+       VersionId    string    `xml:"VersionId"`    // The Object VersionId
+       IsLatest     bool      `xml:"IsLatest"`     // is latest version or not
+       LastModified time.Time `xml:"LastModified"` // Object last modified time
+       Type         string    `xml:"Type"`         // Object type
+       Size         int64     `xml:"Size"`         // Object size
+       ETag         string    `xml:"ETag"`         // Object ETag
+       StorageClass string    `xml:"StorageClass"` // Object storage class (Standard, IA, Archive)
+       Owner        Owner     `xml:"Owner"`        // bucket owner element
+}
+
+// Owner defines Bucket/Object's owner
+type Owner struct {
+       XMLName     xml.Name `xml:"Owner"`
+       ID          string   `xml:"ID"`          // Owner ID
+       DisplayName string   `xml:"DisplayName"` // Owner's display name
+}
+
+// CopyObjectResult defines result object of CopyObject
+type CopyObjectResult struct {
+       XMLName      xml.Name  `xml:"CopyObjectResult"`
+       LastModified time.Time `xml:"LastModified"` // New object's last modified time.
+       ETag         string    `xml:"ETag"`         // New object's ETag
+}
+
+// GetObjectACLResult defines result of GetObjectACL request
+type GetObjectACLResult GetBucketACLResult
+
+type deleteXML struct {
+       XMLName xml.Name       `xml:"Delete"`
+       Objects []DeleteObject `xml:"Object"` // Objects to delete
+       Quiet   bool           `xml:"Quiet"`  // Flag of quiet mode.
+}
+
+// DeleteObject defines the struct for deleting object
+type DeleteObject struct {
+       XMLName   xml.Name `xml:"Object"`
+       Key       string   `xml:"Key"`                 // Object name
+       VersionId string   `xml:"VersionId,omitempty"` // Object VersionId
+}
+
+// DeleteObjectsResult defines result of DeleteObjects request
+type DeleteObjectsResult struct {
+       XMLName        xml.Name
+       DeletedObjects []string // Deleted object key list
+}
+
+// DeleteObjectsResult_inner defines result of DeleteObjects request
+type DeleteObjectVersionsResult struct {
+       XMLName              xml.Name         `xml:"DeleteResult"`
+       DeletedObjectsDetail []DeletedKeyInfo `xml:"Deleted"` // Deleted object detail info
+}
+
+// DeleteKeyInfo defines object delete info
+type DeletedKeyInfo struct {
+       XMLName               xml.Name `xml:"Deleted"`
+       Key                   string   `xml:"Key"`                   // Object key
+       VersionId             string   `xml:"VersionId"`             // VersionId
+       DeleteMarker          bool     `xml:"DeleteMarker"`          // Object DeleteMarker
+       DeleteMarkerVersionId string   `xml:"DeleteMarkerVersionId"` // Object DeleteMarkerVersionId
+}
+
+// InitiateMultipartUploadResult defines result of InitiateMultipartUpload request
+type InitiateMultipartUploadResult struct {
+       XMLName  xml.Name `xml:"InitiateMultipartUploadResult"`
+       Bucket   string   `xml:"Bucket"`   // Bucket name
+       Key      string   `xml:"Key"`      // Object name to upload
+       UploadID string   `xml:"UploadId"` // Generated UploadId
+}
+
+// UploadPart defines the upload/copy part
+type UploadPart struct {
+       XMLName    xml.Name `xml:"Part"`
+       PartNumber int      `xml:"PartNumber"` // Part number
+       ETag       string   `xml:"ETag"`       // ETag value of the part's data
+}
+
+type UploadParts []UploadPart
+
+func (slice UploadParts) Len() int {
+       return len(slice)
+}
+
+func (slice UploadParts) Less(i, j int) bool {
+       return slice[i].PartNumber < slice[j].PartNumber
+}
+
+func (slice UploadParts) Swap(i, j int) {
+       slice[i], slice[j] = slice[j], slice[i]
+}
+
+// UploadPartCopyResult defines result object of multipart copy request.
+type UploadPartCopyResult struct {
+       XMLName      xml.Name  `xml:"CopyPartResult"`
+       LastModified time.Time `xml:"LastModified"` // Last modified time
+       ETag         string    `xml:"ETag"`         // ETag
+}
+
+type completeMultipartUploadXML struct {
+       XMLName xml.Name     `xml:"CompleteMultipartUpload"`
+       Part    []UploadPart `xml:"Part"`
+}
+
+// CompleteMultipartUploadResult defines result object of CompleteMultipartUploadRequest
+type CompleteMultipartUploadResult struct {
+       XMLName  xml.Name `xml:"CompleteMultipartUploadResult"`
+       Location string   `xml:"Location"` // Object URL
+       Bucket   string   `xml:"Bucket"`   // Bucket name
+       ETag     string   `xml:"ETag"`     // Object ETag
+       Key      string   `xml:"Key"`      // Object name
+}
+
+// ListUploadedPartsResult defines result object of ListUploadedParts
+type ListUploadedPartsResult struct {
+       XMLName              xml.Name       `xml:"ListPartsResult"`
+       Bucket               string         `xml:"Bucket"`               // Bucket name
+       Key                  string         `xml:"Key"`                  // Object name
+       UploadID             string         `xml:"UploadId"`             // Upload ID
+       NextPartNumberMarker string         `xml:"NextPartNumberMarker"` // Next part number
+       MaxParts             int            `xml:"MaxParts"`             // Max parts count
+       IsTruncated          bool           `xml:"IsTruncated"`          // Flag indicates all entries returned.false: all entries returned.
+       UploadedParts        []UploadedPart `xml:"Part"`                 // Uploaded parts
+}
+
+// UploadedPart defines uploaded part
+type UploadedPart struct {
+       XMLName      xml.Name  `xml:"Part"`
+       PartNumber   int       `xml:"PartNumber"`   // Part number
+       LastModified time.Time `xml:"LastModified"` // Last modified time
+       ETag         string    `xml:"ETag"`         // ETag cache
+       Size         int       `xml:"Size"`         // Part size
+}
+
+// ListMultipartUploadResult defines result object of ListMultipartUpload
+type ListMultipartUploadResult struct {
+       XMLName            xml.Name            `xml:"ListMultipartUploadsResult"`
+       Bucket             string              `xml:"Bucket"`                // Bucket name
+       Delimiter          string              `xml:"Delimiter"`             // Delimiter for grouping object.
+       Prefix             string              `xml:"Prefix"`                // Object prefix
+       KeyMarker          string              `xml:"KeyMarker"`             // Object key marker
+       UploadIDMarker     string              `xml:"UploadIdMarker"`        // UploadId marker
+       NextKeyMarker      string              `xml:"NextKeyMarker"`         // Next key marker, if not all entries returned.
+       NextUploadIDMarker string              `xml:"NextUploadIdMarker"`    // Next uploadId marker, if not all entries returned.
+       MaxUploads         int                 `xml:"MaxUploads"`            // Max uploads to return
+       IsTruncated        bool                `xml:"IsTruncated"`           // Flag indicates all entries are returned.
+       Uploads            []UncompletedUpload `xml:"Upload"`                // Ongoing uploads (not completed, not aborted)
+       CommonPrefixes     []string            `xml:"CommonPrefixes>Prefix"` // Common prefixes list.
+}
+
+// UncompletedUpload structure wraps an uncompleted upload task
+type UncompletedUpload struct {
+       XMLName   xml.Name  `xml:"Upload"`
+       Key       string    `xml:"Key"`       // Object name
+       UploadID  string    `xml:"UploadId"`  // The UploadId
+       Initiated time.Time `xml:"Initiated"` // Initialization time in the format such as 2012-02-23T04:18:23.000Z
+}
+
+// ProcessObjectResult defines result object of ProcessObject
+type ProcessObjectResult struct {
+       Bucket   string `json:"bucket"`
+       FileSize int    `json:"fileSize"`
+       Object   string `json:"object"`
+       Status   string `json:"status"`
+}
+
+// decodeDeleteObjectsResult decodes deleting objects result in URL encoding
+func decodeDeleteObjectsResult(result *DeleteObjectVersionsResult) error {
+       var err error
+       for i := 0; i < len(result.DeletedObjectsDetail); i++ {
+               result.DeletedObjectsDetail[i].Key, err = url.QueryUnescape(result.DeletedObjectsDetail[i].Key)
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// decodeListObjectsResult decodes list objects result in URL encoding
+func decodeListObjectsResult(result *ListObjectsResult) error {
+       var err error
+       result.Prefix, err = url.QueryUnescape(result.Prefix)
+       if err != nil {
+               return err
+       }
+       result.Marker, err = url.QueryUnescape(result.Marker)
+       if err != nil {
+               return err
+       }
+       result.Delimiter, err = url.QueryUnescape(result.Delimiter)
+       if err != nil {
+               return err
+       }
+       result.NextMarker, err = url.QueryUnescape(result.NextMarker)
+       if err != nil {
+               return err
+       }
+       for i := 0; i < len(result.Objects); i++ {
+               result.Objects[i].Key, err = url.QueryUnescape(result.Objects[i].Key)
+               if err != nil {
+                       return err
+               }
+       }
+       for i := 0; i < len(result.CommonPrefixes); i++ {
+               result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i])
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// decodeListObjectsResult decodes list objects result in URL encoding
+func decodeListObjectsResultV2(result *ListObjectsResultV2) error {
+       var err error
+       result.Prefix, err = url.QueryUnescape(result.Prefix)
+       if err != nil {
+               return err
+       }
+       result.StartAfter, err = url.QueryUnescape(result.StartAfter)
+       if err != nil {
+               return err
+       }
+       result.Delimiter, err = url.QueryUnescape(result.Delimiter)
+       if err != nil {
+               return err
+       }
+       result.NextContinuationToken, err = url.QueryUnescape(result.NextContinuationToken)
+       if err != nil {
+               return err
+       }
+       for i := 0; i < len(result.Objects); i++ {
+               result.Objects[i].Key, err = url.QueryUnescape(result.Objects[i].Key)
+               if err != nil {
+                       return err
+               }
+       }
+       for i := 0; i < len(result.CommonPrefixes); i++ {
+               result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i])
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// decodeListObjectVersionsResult decodes list version objects result in URL encoding
+func decodeListObjectVersionsResult(result *ListObjectVersionsResult) error {
+       var err error
+
+       // decode:Delimiter
+       result.Delimiter, err = url.QueryUnescape(result.Delimiter)
+       if err != nil {
+               return err
+       }
+
+       // decode Prefix
+       result.Prefix, err = url.QueryUnescape(result.Prefix)
+       if err != nil {
+               return err
+       }
+
+       // decode KeyMarker
+       result.KeyMarker, err = url.QueryUnescape(result.KeyMarker)
+       if err != nil {
+               return err
+       }
+
+       // decode VersionIdMarker
+       result.VersionIdMarker, err = url.QueryUnescape(result.VersionIdMarker)
+       if err != nil {
+               return err
+       }
+
+       // decode NextKeyMarker
+       result.NextKeyMarker, err = url.QueryUnescape(result.NextKeyMarker)
+       if err != nil {
+               return err
+       }
+
+       // decode NextVersionIdMarker
+       result.NextVersionIdMarker, err = url.QueryUnescape(result.NextVersionIdMarker)
+       if err != nil {
+               return err
+       }
+
+       // decode CommonPrefixes
+       for i := 0; i < len(result.CommonPrefixes); i++ {
+               result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i])
+               if err != nil {
+                       return err
+               }
+       }
+
+       // decode deleteMarker
+       for i := 0; i < len(result.ObjectDeleteMarkers); i++ {
+               result.ObjectDeleteMarkers[i].Key, err = url.QueryUnescape(result.ObjectDeleteMarkers[i].Key)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // decode ObjectVersions
+       for i := 0; i < len(result.ObjectVersions); i++ {
+               result.ObjectVersions[i].Key, err = url.QueryUnescape(result.ObjectVersions[i].Key)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+// decodeListUploadedPartsResult decodes
+func decodeListUploadedPartsResult(result *ListUploadedPartsResult) error {
+       var err error
+       result.Key, err = url.QueryUnescape(result.Key)
+       if err != nil {
+               return err
+       }
+       return nil
+}
+
+// decodeListMultipartUploadResult decodes list multipart upload result in URL encoding
+func decodeListMultipartUploadResult(result *ListMultipartUploadResult) error {
+       var err error
+       result.Prefix, err = url.QueryUnescape(result.Prefix)
+       if err != nil {
+               return err
+       }
+       result.Delimiter, err = url.QueryUnescape(result.Delimiter)
+       if err != nil {
+               return err
+       }
+       result.KeyMarker, err = url.QueryUnescape(result.KeyMarker)
+       if err != nil {
+               return err
+       }
+       result.NextKeyMarker, err = url.QueryUnescape(result.NextKeyMarker)
+       if err != nil {
+               return err
+       }
+       for i := 0; i < len(result.Uploads); i++ {
+               result.Uploads[i].Key, err = url.QueryUnescape(result.Uploads[i].Key)
+               if err != nil {
+                       return err
+               }
+       }
+       for i := 0; i < len(result.CommonPrefixes); i++ {
+               result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i])
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// createBucketConfiguration defines the configuration for creating a bucket.
+type createBucketConfiguration struct {
+       XMLName            xml.Name           `xml:"CreateBucketConfiguration"`
+       StorageClass       StorageClassType   `xml:"StorageClass,omitempty"`
+       DataRedundancyType DataRedundancyType `xml:"DataRedundancyType,omitempty"`
+}
+
+// LiveChannelConfiguration defines the configuration for live-channel
+type LiveChannelConfiguration struct {
+       XMLName     xml.Name          `xml:"LiveChannelConfiguration"`
+       Description string            `xml:"Description,omitempty"` //Description of live-channel, up to 128 bytes
+       Status      string            `xml:"Status,omitempty"`      //\bSpecify the status of livechannel
+       Target      LiveChannelTarget `xml:"Target"`                //target configuration of live-channel
+       // use point instead of struct to avoid omit empty snapshot
+       Snapshot *LiveChannelSnapshot `xml:"Snapshot,omitempty"` //snapshot configuration of live-channel
+}
+
+// LiveChannelTarget target configuration of live-channel
+type LiveChannelTarget struct {
+       XMLName      xml.Name `xml:"Target"`
+       Type         string   `xml:"Type"`                   //the type of object, only supports HLS
+       FragDuration int      `xml:"FragDuration,omitempty"` //the length of each ts object (in seconds), in the range [1,100]
+       FragCount    int      `xml:"FragCount,omitempty"`    //the number of ts objects in the m3u8 object, in the range of [1,100]
+       PlaylistName string   `xml:"PlaylistName,omitempty"` //the name of m3u8 object, which must end with ".m3u8" and the length range is [6,128]
+}
+
+// LiveChannelSnapshot snapshot configuration of live-channel
+type LiveChannelSnapshot struct {
+       XMLName     xml.Name `xml:"Snapshot"`
+       RoleName    string   `xml:"RoleName,omitempty"`    //The role of snapshot operations, it sholud has write permission of DestBucket and the permission to send messages to the NotifyTopic.
+       DestBucket  string   `xml:"DestBucket,omitempty"`  //Bucket the snapshots will be written to. should be the same owner as the source bucket.
+       NotifyTopic string   `xml:"NotifyTopic,omitempty"` //Topics of MNS for notifying users of high frequency screenshot operation results
+       Interval    int      `xml:"Interval,omitempty"`    //interval of snapshots, threre is no snapshot if no I-frame during the interval time
+}
+
+// CreateLiveChannelResult the result of crete live-channel
+type CreateLiveChannelResult struct {
+       XMLName     xml.Name `xml:"CreateLiveChannelResult"`
+       PublishUrls []string `xml:"PublishUrls>Url"` //push urls list
+       PlayUrls    []string `xml:"PlayUrls>Url"`    //play urls list
+}
+
+// LiveChannelStat the result of get live-channel state
+type LiveChannelStat struct {
+       XMLName       xml.Name         `xml:"LiveChannelStat"`
+       Status        string           `xml:"Status"`        //Current push status of live-channel: Disabled,Live,Idle
+       ConnectedTime time.Time        `xml:"ConnectedTime"` //The time when the client starts pushing, format: ISO8601
+       RemoteAddr    string           `xml:"RemoteAddr"`    //The ip address of the client
+       Video         LiveChannelVideo `xml:"Video"`         //Video stream information
+       Audio         LiveChannelAudio `xml:"Audio"`         //Audio stream information
+}
+
+// LiveChannelVideo video stream information
+type LiveChannelVideo struct {
+       XMLName   xml.Name `xml:"Video"`
+       Width     int      `xml:"Width"`     //Width (unit: pixels)
+       Height    int      `xml:"Height"`    //Height (unit: pixels)
+       FrameRate int      `xml:"FrameRate"` //FramRate
+       Bandwidth int      `xml:"Bandwidth"` //Bandwidth (unit: B/s)
+}
+
+// LiveChannelAudio audio stream information
+type LiveChannelAudio struct {
+       XMLName    xml.Name `xml:"Audio"`
+       SampleRate int      `xml:"SampleRate"` //SampleRate
+       Bandwidth  int      `xml:"Bandwidth"`  //Bandwidth (unit: B/s)
+       Codec      string   `xml:"Codec"`      //Encoding forma
+}
+
+// LiveChannelHistory the result of GetLiveChannelHistory, at most return up to lastest 10 push records
+type LiveChannelHistory struct {
+       XMLName xml.Name     `xml:"LiveChannelHistory"`
+       Record  []LiveRecord `xml:"LiveRecord"` //push records list
+}
+
+// LiveRecord push recode
+type LiveRecord struct {
+       XMLName    xml.Name  `xml:"LiveRecord"`
+       StartTime  time.Time `xml:"StartTime"`  //StartTime, format: ISO8601
+       EndTime    time.Time `xml:"EndTime"`    //EndTime, format: ISO8601
+       RemoteAddr string    `xml:"RemoteAddr"` //The ip address of remote client
+}
+
+// ListLiveChannelResult the result of ListLiveChannel
+type ListLiveChannelResult struct {
+       XMLName     xml.Name          `xml:"ListLiveChannelResult"`
+       Prefix      string            `xml:"Prefix"`      //Filter by the name start with the value of "Prefix"
+       Marker      string            `xml:"Marker"`      //cursor from which starting list
+       MaxKeys     int               `xml:"MaxKeys"`     //The maximum count returned. the default value is 100. it cannot be greater than 1000.
+       IsTruncated bool              `xml:"IsTruncated"` //Indicates whether all results have been returned, "true" indicates partial results returned while "false" indicates all results have been returned
+       NextMarker  string            `xml:"NextMarker"`  //NextMarker indicate the Marker value of the next request
+       LiveChannel []LiveChannelInfo `xml:"LiveChannel"` //The infomation of live-channel
+}
+
+// LiveChannelInfo the infomation of live-channel
+type LiveChannelInfo struct {
+       XMLName      xml.Name  `xml:"LiveChannel"`
+       Name         string    `xml:"Name"`            //The name of live-channel
+       Description  string    `xml:"Description"`     //Description of live-channel
+       Status       string    `xml:"Status"`          //Status: disabled or enabled
+       LastModified time.Time `xml:"LastModified"`    //Last modification time, format: ISO8601
+       PublishUrls  []string  `xml:"PublishUrls>Url"` //push urls list
+       PlayUrls     []string  `xml:"PlayUrls>Url"`    //play urls list
+}
+
+// Tag a tag for the object
+type Tag struct {
+       XMLName xml.Name `xml:"Tag"`
+       Key     string   `xml:"Key"`
+       Value   string   `xml:"Value"`
+}
+
+// Tagging tagset for the object
+type Tagging struct {
+       XMLName xml.Name `xml:"Tagging"`
+       Tags    []Tag    `xml:"TagSet>Tag,omitempty"`
+}
+
+// for GetObjectTagging return value
+type GetObjectTaggingResult Tagging
+
+// VersioningConfig for the bucket
+type VersioningConfig struct {
+       XMLName xml.Name `xml:"VersioningConfiguration"`
+       Status  string   `xml:"Status"`
+}
+
+type GetBucketVersioningResult VersioningConfig
+
+// Server Encryption rule for the bucket
+type ServerEncryptionRule struct {
+       XMLName    xml.Name       `xml:"ServerSideEncryptionRule"`
+       SSEDefault SSEDefaultRule `xml:"ApplyServerSideEncryptionByDefault"`
+}
+
+// Server Encryption deafult rule for the bucket
+type SSEDefaultRule struct {
+       XMLName           xml.Name `xml:"ApplyServerSideEncryptionByDefault"`
+       SSEAlgorithm      string   `xml:"SSEAlgorithm,omitempty"`
+       KMSMasterKeyID    string   `xml:"KMSMasterKeyID,omitempty"`
+       KMSDataEncryption string   `xml:"KMSDataEncryption,,omitempty"`
+}
+
+type GetBucketEncryptionResult ServerEncryptionRule
+type GetBucketTaggingResult Tagging
+
+type BucketStat struct {
+       XMLName              xml.Name `xml:"BucketStat"`
+       Storage              int64    `xml:"Storage"`
+       ObjectCount          int64    `xml:"ObjectCount"`
+       MultipartUploadCount int64    `xml:"MultipartUploadCount"`
+}
+type GetBucketStatResult BucketStat
+
+// RequestPaymentConfiguration define the request payment configuration
+type RequestPaymentConfiguration struct {
+       XMLName xml.Name `xml:"RequestPaymentConfiguration"`
+       Payer   string   `xml:"Payer,omitempty"`
+}
+
+// BucketQoSConfiguration define QoS configuration
+type BucketQoSConfiguration struct {
+       XMLName                   xml.Name `xml:"QoSConfiguration"`
+       TotalUploadBandwidth      *int     `xml:"TotalUploadBandwidth"`      // Total upload bandwidth
+       IntranetUploadBandwidth   *int     `xml:"IntranetUploadBandwidth"`   // Intranet upload bandwidth
+       ExtranetUploadBandwidth   *int     `xml:"ExtranetUploadBandwidth"`   // Extranet upload bandwidth
+       TotalDownloadBandwidth    *int     `xml:"TotalDownloadBandwidth"`    // Total download bandwidth
+       IntranetDownloadBandwidth *int     `xml:"IntranetDownloadBandwidth"` // Intranet download bandwidth
+       ExtranetDownloadBandwidth *int     `xml:"ExtranetDownloadBandwidth"` // Extranet download bandwidth
+       TotalQPS                  *int     `xml:"TotalQps"`                  // Total Qps
+       IntranetQPS               *int     `xml:"IntranetQps"`               // Intranet Qps
+       ExtranetQPS               *int     `xml:"ExtranetQps"`               // Extranet Qps
+}
+
+// UserQoSConfiguration define QoS and Range configuration
+type UserQoSConfiguration struct {
+       XMLName xml.Name `xml:"QoSConfiguration"`
+       Region  string   `xml:"Region,omitempty"` // Effective area of Qos configuration
+       BucketQoSConfiguration
+}
+
+//////////////////////////////////////////////////////////////
+/////////////////// Select OBject ////////////////////////////
+//////////////////////////////////////////////////////////////
+
+type CsvMetaRequest struct {
+       XMLName            xml.Name           `xml:"CsvMetaRequest"`
+       InputSerialization InputSerialization `xml:"InputSerialization"`
+       OverwriteIfExists  *bool              `xml:"OverwriteIfExists,omitempty"`
+}
+
+// encodeBase64 encode base64 of the CreateSelectObjectMeta api request params
+func (meta *CsvMetaRequest) encodeBase64() {
+       meta.InputSerialization.CSV.RecordDelimiter =
+               base64.StdEncoding.EncodeToString([]byte(meta.InputSerialization.CSV.RecordDelimiter))
+       meta.InputSerialization.CSV.FieldDelimiter =
+               base64.StdEncoding.EncodeToString([]byte(meta.InputSerialization.CSV.FieldDelimiter))
+       meta.InputSerialization.CSV.QuoteCharacter =
+               base64.StdEncoding.EncodeToString([]byte(meta.InputSerialization.CSV.QuoteCharacter))
+}
+
+type JsonMetaRequest struct {
+       XMLName            xml.Name           `xml:"JsonMetaRequest"`
+       InputSerialization InputSerialization `xml:"InputSerialization"`
+       OverwriteIfExists  *bool              `xml:"OverwriteIfExists,omitempty"`
+}
+
+type InputSerialization struct {
+       XMLName         xml.Name `xml:"InputSerialization"`
+       CSV             CSV      `xml:CSV,omitempty`
+       JSON            JSON     `xml:JSON,omitempty`
+       CompressionType string   `xml:"CompressionType,omitempty"`
+}
+type CSV struct {
+       XMLName         xml.Name `xml:"CSV"`
+       RecordDelimiter string   `xml:"RecordDelimiter,omitempty"`
+       FieldDelimiter  string   `xml:"FieldDelimiter,omitempty"`
+       QuoteCharacter  string   `xml:"QuoteCharacter,omitempty"`
+}
+
+type JSON struct {
+       XMLName  xml.Name `xml:"JSON"`
+       JSONType string   `xml:"Type,omitempty"`
+}
+
+// SelectRequest is for the SelectObject request params of json file
+type SelectRequest struct {
+       XMLName                   xml.Name                  `xml:"SelectRequest"`
+       Expression                string                    `xml:"Expression"`
+       InputSerializationSelect  InputSerializationSelect  `xml:"InputSerialization"`
+       OutputSerializationSelect OutputSerializationSelect `xml:"OutputSerialization"`
+       SelectOptions             SelectOptions             `xml:"Options,omitempty"`
+}
+type InputSerializationSelect struct {
+       XMLName         xml.Name        `xml:"InputSerialization"`
+       CsvBodyInput    CSVSelectInput  `xml:CSV,omitempty`
+       JsonBodyInput   JSONSelectInput `xml:JSON,omitempty`
+       CompressionType string          `xml:"CompressionType,omitempty"`
+}
+type CSVSelectInput struct {
+       XMLName          xml.Name `xml:"CSV"`
+       FileHeaderInfo   string   `xml:"FileHeaderInfo,omitempty"`
+       RecordDelimiter  string   `xml:"RecordDelimiter,omitempty"`
+       FieldDelimiter   string   `xml:"FieldDelimiter,omitempty"`
+       QuoteCharacter   string   `xml:"QuoteCharacter,omitempty"`
+       CommentCharacter string   `xml:"CommentCharacter,omitempty"`
+       Range            string   `xml:"Range,omitempty"`
+       SplitRange       string
+}
+type JSONSelectInput struct {
+       XMLName                 xml.Name `xml:"JSON"`
+       JSONType                string   `xml:"Type,omitempty"`
+       Range                   string   `xml:"Range,omitempty"`
+       ParseJSONNumberAsString *bool    `xml:"ParseJsonNumberAsString"`
+       SplitRange              string
+}
+
+func (jsonInput *JSONSelectInput) JsonIsEmpty() bool {
+       if jsonInput.JSONType != "" {
+               return false
+       }
+       return true
+}
+
+type OutputSerializationSelect struct {
+       XMLName          xml.Name         `xml:"OutputSerialization"`
+       CsvBodyOutput    CSVSelectOutput  `xml:CSV,omitempty`
+       JsonBodyOutput   JSONSelectOutput `xml:JSON,omitempty`
+       OutputRawData    *bool            `xml:"OutputRawData,omitempty"`
+       KeepAllColumns   *bool            `xml:"KeepAllColumns,omitempty"`
+       EnablePayloadCrc *bool            `xml:"EnablePayloadCrc,omitempty"`
+       OutputHeader     *bool            `xml:"OutputHeader,omitempty"`
+}
+type CSVSelectOutput struct {
+       XMLName         xml.Name `xml:"CSV"`
+       RecordDelimiter string   `xml:"RecordDelimiter,omitempty"`
+       FieldDelimiter  string   `xml:"FieldDelimiter,omitempty"`
+}
+type JSONSelectOutput struct {
+       XMLName         xml.Name `xml:"JSON"`
+       RecordDelimiter string   `xml:"RecordDelimiter,omitempty"`
+}
+
+func (selectReq *SelectRequest) encodeBase64() {
+       if selectReq.InputSerializationSelect.JsonBodyInput.JsonIsEmpty() {
+               selectReq.csvEncodeBase64()
+       } else {
+               selectReq.jsonEncodeBase64()
+       }
+}
+
+// csvEncodeBase64 encode base64 of the SelectObject api request params
+func (selectReq *SelectRequest) csvEncodeBase64() {
+       selectReq.Expression = base64.StdEncoding.EncodeToString([]byte(selectReq.Expression))
+       selectReq.InputSerializationSelect.CsvBodyInput.RecordDelimiter =
+               base64.StdEncoding.EncodeToString([]byte(selectReq.InputSerializationSelect.CsvBodyInput.RecordDelimiter))
+       selectReq.InputSerializationSelect.CsvBodyInput.FieldDelimiter =
+               base64.StdEncoding.EncodeToString([]byte(selectReq.InputSerializationSelect.CsvBodyInput.FieldDelimiter))
+       selectReq.InputSerializationSelect.CsvBodyInput.QuoteCharacter =
+               base64.StdEncoding.EncodeToString([]byte(selectReq.InputSerializationSelect.CsvBodyInput.QuoteCharacter))
+       selectReq.InputSerializationSelect.CsvBodyInput.CommentCharacter =
+               base64.StdEncoding.EncodeToString([]byte(selectReq.InputSerializationSelect.CsvBodyInput.CommentCharacter))
+       selectReq.OutputSerializationSelect.CsvBodyOutput.FieldDelimiter =
+               base64.StdEncoding.EncodeToString([]byte(selectReq.OutputSerializationSelect.CsvBodyOutput.FieldDelimiter))
+       selectReq.OutputSerializationSelect.CsvBodyOutput.RecordDelimiter =
+               base64.StdEncoding.EncodeToString([]byte(selectReq.OutputSerializationSelect.CsvBodyOutput.RecordDelimiter))
+
+       // handle Range
+       if selectReq.InputSerializationSelect.CsvBodyInput.Range != "" {
+               selectReq.InputSerializationSelect.CsvBodyInput.Range = "line-range=" + selectReq.InputSerializationSelect.CsvBodyInput.Range
+       }
+
+       if selectReq.InputSerializationSelect.CsvBodyInput.SplitRange != "" {
+               selectReq.InputSerializationSelect.CsvBodyInput.Range = "split-range=" + selectReq.InputSerializationSelect.CsvBodyInput.SplitRange
+       }
+}
+
+// jsonEncodeBase64 encode base64 of the SelectObject api request params
+func (selectReq *SelectRequest) jsonEncodeBase64() {
+       selectReq.Expression = base64.StdEncoding.EncodeToString([]byte(selectReq.Expression))
+       selectReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter =
+               base64.StdEncoding.EncodeToString([]byte(selectReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter))
+
+       // handle Range
+       if selectReq.InputSerializationSelect.JsonBodyInput.Range != "" {
+               selectReq.InputSerializationSelect.JsonBodyInput.Range = "line-range=" + selectReq.InputSerializationSelect.JsonBodyInput.Range
+       }
+
+       if selectReq.InputSerializationSelect.JsonBodyInput.SplitRange != "" {
+               selectReq.InputSerializationSelect.JsonBodyInput.Range = "split-range=" + selectReq.InputSerializationSelect.JsonBodyInput.SplitRange
+       }
+}
+
+// CsvOptions is a element in the SelectObject api request's params
+type SelectOptions struct {
+       XMLName                  xml.Name `xml:"Options"`
+       SkipPartialDataRecord    *bool    `xml:"SkipPartialDataRecord,omitempty"`
+       MaxSkippedRecordsAllowed string   `xml:"MaxSkippedRecordsAllowed,omitempty"`
+}
+
+// SelectObjectResult is the SelectObject api's return
+type SelectObjectResult struct {
+       Version          byte
+       FrameType        int32
+       PayloadLength    int32
+       HeaderCheckSum   uint32
+       Offset           uint64
+       Data             string           // DataFrame
+       EndFrame         EndFrame         // EndFrame
+       MetaEndFrameCSV  MetaEndFrameCSV  // MetaEndFrameCSV
+       MetaEndFrameJSON MetaEndFrameJSON // MetaEndFrameJSON
+       PayloadChecksum  uint32
+       ReadFlagInfo
+}
+
+// ReadFlagInfo if reading the frame data, recode the reading status
+type ReadFlagInfo struct {
+       OpenLine            bool
+       ConsumedBytesLength int32
+       EnablePayloadCrc    bool
+       OutputRawData       bool
+}
+
+// EndFrame is EndFrameType of SelectObject api
+type EndFrame struct {
+       TotalScanned   int64
+       HTTPStatusCode int32
+       ErrorMsg       string
+}
+
+// MetaEndFrameCSV is MetaEndFrameCSVType of CreateSelectObjectMeta
+type MetaEndFrameCSV struct {
+       TotalScanned int64
+       Status       int32
+       SplitsCount  int32
+       RowsCount    int64
+       ColumnsCount int32
+       ErrorMsg     string
+}
+
+// MetaEndFrameJSON is MetaEndFrameJSON of CreateSelectObjectMeta
+type MetaEndFrameJSON struct {
+       TotalScanned int64
+       Status       int32
+       SplitsCount  int32
+       RowsCount    int64
+       ErrorMsg     string
+}
+
+// InventoryConfiguration is Inventory config
+type InventoryConfiguration struct {
+       XMLName                xml.Name             `xml:"InventoryConfiguration"`
+       Id                     string               `xml:"Id,omitempty"`
+       IsEnabled              *bool                `xml:"IsEnabled,omitempty"`
+       Prefix                 string               `xml:"Filter>Prefix,omitempty"`
+       OSSBucketDestination   OSSBucketDestination `xml:"Destination>OSSBucketDestination,omitempty"`
+       Frequency              string               `xml:"Schedule>Frequency,omitempty"`
+       IncludedObjectVersions string               `xml:"IncludedObjectVersions,omitempty"`
+       OptionalFields         OptionalFields       `xml:OptionalFields,omitempty`
+}
+
+type OptionalFields struct {
+       XMLName xml.Name `xml:"OptionalFields,omitempty`
+       Field   []string `xml:"Field,omitempty`
+}
+
+type OSSBucketDestination struct {
+       XMLName    xml.Name       `xml:"OSSBucketDestination"`
+       Format     string         `xml:"Format,omitempty"`
+       AccountId  string         `xml:"AccountId,omitempty"`
+       RoleArn    string         `xml:"RoleArn,omitempty"`
+       Bucket     string         `xml:"Bucket,omitempty"`
+       Prefix     string         `xml:"Prefix,omitempty"`
+       Encryption *InvEncryption `xml:"Encryption,omitempty"`
+}
+
+type InvEncryption struct {
+       XMLName xml.Name   `xml:"Encryption"`
+       SseOss  *InvSseOss `xml:"SSE-OSS"`
+       SseKms  *InvSseKms `xml:"SSE-KMS"`
+}
+
+type InvSseOss struct {
+       XMLName xml.Name `xml:"SSE-OSS"`
+}
+
+type InvSseKms struct {
+       XMLName xml.Name `xml:"SSE-KMS"`
+       KmsId   string   `xml:"KeyId,omitempty"`
+}
+
+type ListInventoryConfigurationsResult struct {
+       XMLName                xml.Name                 `xml:"ListInventoryConfigurationsResult"`
+       InventoryConfiguration []InventoryConfiguration `xml:"InventoryConfiguration,omitempty`
+       IsTruncated            *bool                    `xml:"IsTruncated,omitempty"`
+       NextContinuationToken  string                   `xml:"NextContinuationToken,omitempty"`
+}
+
+// RestoreConfiguration for RestoreObject
+type RestoreConfiguration struct {
+       XMLName xml.Name `xml:"RestoreRequest"`
+       Days    int32    `xml:"Days,omitempty"`
+       Tier    string   `xml:"JobParameters>Tier,omitempty"`
+}
+
+// AsyncFetchTaskConfiguration for SetBucketAsyncFetchTask
+type AsyncFetchTaskConfiguration struct {
+       XMLName       xml.Name `xml:"AsyncFetchTaskConfiguration"`
+       Url           string   `xml:"Url,omitempty"`
+       Object        string   `xml:"Object,omitempty"`
+       Host          string   `xml:"Host,omitempty"`
+       ContentMD5    string   `xml:"ContentMD5,omitempty"`
+       Callback      string   `xml:"Callback,omitempty"`
+       StorageClass  string   `xml:"StorageClass,omitempty"`
+       IgnoreSameKey bool     `xml:"IgnoreSameKey"`
+}
+
+// AsyncFetchTaskResult for SetBucketAsyncFetchTask result
+type AsyncFetchTaskResult struct {
+       XMLName xml.Name `xml:"AsyncFetchTaskResult"`
+       TaskId  string   `xml:"TaskId,omitempty"`
+}
+
+// AsynFetchTaskInfo for GetBucketAsyncFetchTask result
+type AsynFetchTaskInfo struct {
+       XMLName  xml.Name      `xml:"AsyncFetchTaskInfo"`
+       TaskId   string        `xml:"TaskId,omitempty"`
+       State    string        `xml:"State,omitempty"`
+       ErrorMsg string        `xml:"ErrorMsg,omitempty"`
+       TaskInfo AsyncTaskInfo `xml:"TaskInfo,omitempty"`
+}
+
+// AsyncTaskInfo for async task information
+type AsyncTaskInfo struct {
+       XMLName       xml.Name `xml:"TaskInfo"`
+       Url           string   `xml:"Url,omitempty"`
+       Object        string   `xml:"Object,omitempty"`
+       Host          string   `xml:"Host,omitempty"`
+       ContentMD5    string   `xml:"ContentMD5,omitempty"`
+       Callback      string   `xml:"Callback,omitempty"`
+       StorageClass  string   `xml:"StorageClass,omitempty"`
+       IgnoreSameKey bool     `xml:"IgnoreSameKey"`
+}
+
+// InitiateWormConfiguration define InitiateBucketWorm configuration
+type InitiateWormConfiguration struct {
+       XMLName               xml.Name `xml:"InitiateWormConfiguration"`
+       RetentionPeriodInDays int      `xml:"RetentionPeriodInDays"` // specify retention days
+}
+
+// ExtendWormConfiguration define ExtendWormConfiguration configuration
+type ExtendWormConfiguration struct {
+       XMLName               xml.Name `xml:"ExtendWormConfiguration"`
+       RetentionPeriodInDays int      `xml:"RetentionPeriodInDays"` // specify retention days
+}
+
+// WormConfiguration define WormConfiguration
+type WormConfiguration struct {
+       XMLName               xml.Name `xml:"WormConfiguration"`
+       WormId                string   `xml:"WormId,omitempty"`
+       State                 string   `xml:"State,omitempty"`
+       RetentionPeriodInDays int      `xml:"RetentionPeriodInDays"` // specify retention days
+       CreationDate          string   `xml:"CreationDate,omitempty"`
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type_test.go
new file mode 100644 (file)
index 0000000..caeaa05
--- /dev/null
@@ -0,0 +1,391 @@
+package oss
+
+import (
+       "net/url"
+       "sort"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssTypeSuite struct{}
+
+var _ = Suite(&OssTypeSuite{})
+
+var (
+       goStr     = "go go + go <> go"
+       chnStr    = "试问闲情几许"
+       goURLStr  = url.QueryEscape(goStr)
+       chnURLStr = url.QueryEscape(chnStr)
+)
+
+func (s *OssTypeSuite) TestDecodeDeleteObjectsResult(c *C) {
+       var res DeleteObjectVersionsResult
+       err := decodeDeleteObjectsResult(&res)
+       c.Assert(err, IsNil)
+
+       res.DeletedObjectsDetail = []DeletedKeyInfo{DeletedKeyInfo{Key: ""}}
+       err = decodeDeleteObjectsResult(&res)
+       c.Assert(err, IsNil)
+       c.Assert(res.DeletedObjectsDetail[0].Key, Equals, "")
+
+       res.DeletedObjectsDetail = []DeletedKeyInfo{DeletedKeyInfo{Key: goURLStr}, DeletedKeyInfo{Key: chnURLStr}}
+       err = decodeDeleteObjectsResult(&res)
+       c.Assert(err, IsNil)
+       c.Assert(res.DeletedObjectsDetail[0].Key, Equals, goStr)
+       c.Assert(res.DeletedObjectsDetail[1].Key, Equals, chnStr)
+}
+
+func (s *OssTypeSuite) TestDecodeListObjectsResult(c *C) {
+       var res ListObjectsResult
+       err := decodeListObjectsResult(&res)
+       c.Assert(err, IsNil)
+
+       res = ListObjectsResult{}
+       err = decodeListObjectsResult(&res)
+       c.Assert(err, IsNil)
+
+       res = ListObjectsResult{Prefix: goURLStr, Marker: goURLStr,
+               Delimiter: goURLStr, NextMarker: goURLStr,
+               Objects:        []ObjectProperties{{Key: chnURLStr}},
+               CommonPrefixes: []string{chnURLStr}}
+
+       err = decodeListObjectsResult(&res)
+       c.Assert(err, IsNil)
+
+       c.Assert(res.Prefix, Equals, goStr)
+       c.Assert(res.Marker, Equals, goStr)
+       c.Assert(res.Delimiter, Equals, goStr)
+       c.Assert(res.NextMarker, Equals, goStr)
+       c.Assert(res.Objects[0].Key, Equals, chnStr)
+       c.Assert(res.CommonPrefixes[0], Equals, chnStr)
+}
+
+func (s *OssTypeSuite) TestDecodeListMultipartUploadResult(c *C) {
+       res := ListMultipartUploadResult{}
+       err := decodeListMultipartUploadResult(&res)
+       c.Assert(err, IsNil)
+
+       res = ListMultipartUploadResult{Prefix: goURLStr, KeyMarker: goURLStr,
+               Delimiter: goURLStr, NextKeyMarker: goURLStr,
+               Uploads: []UncompletedUpload{{Key: chnURLStr}}}
+
+       err = decodeListMultipartUploadResult(&res)
+       c.Assert(err, IsNil)
+
+       c.Assert(res.Prefix, Equals, goStr)
+       c.Assert(res.KeyMarker, Equals, goStr)
+       c.Assert(res.Delimiter, Equals, goStr)
+       c.Assert(res.NextKeyMarker, Equals, goStr)
+       c.Assert(res.Uploads[0].Key, Equals, chnStr)
+}
+
+func (s *OssTypeSuite) TestSortUploadPart(c *C) {
+       parts := []UploadPart{}
+
+       sort.Sort(UploadParts(parts))
+       c.Assert(len(parts), Equals, 0)
+
+       parts = []UploadPart{
+               {PartNumber: 5, ETag: "E5"},
+               {PartNumber: 1, ETag: "E1"},
+               {PartNumber: 4, ETag: "E4"},
+               {PartNumber: 2, ETag: "E2"},
+               {PartNumber: 3, ETag: "E3"},
+       }
+
+       sort.Sort(UploadParts(parts))
+
+       c.Assert(parts[0].PartNumber, Equals, 1)
+       c.Assert(parts[0].ETag, Equals, "E1")
+       c.Assert(parts[1].PartNumber, Equals, 2)
+       c.Assert(parts[1].ETag, Equals, "E2")
+       c.Assert(parts[2].PartNumber, Equals, 3)
+       c.Assert(parts[2].ETag, Equals, "E3")
+       c.Assert(parts[3].PartNumber, Equals, 4)
+       c.Assert(parts[3].ETag, Equals, "E4")
+       c.Assert(parts[4].PartNumber, Equals, 5)
+       c.Assert(parts[4].ETag, Equals, "E5")
+}
+
+func (s *OssTypeSuite) TestValidateLifecleRules(c *C) {
+       expiration := LifecycleExpiration{
+               Days:              30,
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+       }
+       rule := LifecycleRule{
+               ID:         "ruleID",
+               Prefix:     "prefix",
+               Status:     "Enabled",
+               Expiration: &expiration,
+       }
+       rules := []LifecycleRule{rule}
+       err := verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       expiration = LifecycleExpiration{
+               Date:              "2015-11-11T00:00:00.000Z",
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+       }
+       rule = LifecycleRule{
+               ID:         "ruleID",
+               Prefix:     "prefix",
+               Status:     "Enabled",
+               Expiration: &expiration,
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       expiration = LifecycleExpiration{
+               Days:              0,
+               CreatedBeforeDate: "",
+               Date:              "",
+       }
+       rule = LifecycleRule{
+               ID:         "ruleID",
+               Prefix:     "prefix",
+               Status:     "Enabled",
+               Expiration: &expiration,
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       abortMPU := LifecycleAbortMultipartUpload{
+               Days:              30,
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+       }
+       rule = LifecycleRule{
+               ID:                   "ruleID",
+               Prefix:               "prefix",
+               Status:               "Enabled",
+               AbortMultipartUpload: &abortMPU,
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, NotNil)
+
+       abortMPU = LifecycleAbortMultipartUpload{
+               Days:              0,
+               CreatedBeforeDate: "",
+       }
+       rule = LifecycleRule{
+               ID:                   "ruleID",
+               Prefix:               "prefix",
+               Status:               "Enabled",
+               AbortMultipartUpload: &abortMPU,
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, NotNil)
+
+       transition := LifecycleTransition{
+               Days:              30,
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+               StorageClass:      StorageIA,
+       }
+       rule = LifecycleRule{
+               ID:          "ruleID",
+               Prefix:      "prefix",
+               Status:      "Enabled",
+               Transitions: []LifecycleTransition{transition},
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, NotNil)
+
+       transition = LifecycleTransition{
+               Days:              0,
+               CreatedBeforeDate: "",
+               StorageClass:      StorageIA,
+       }
+       rule = LifecycleRule{
+               ID:          "ruleID",
+               Prefix:      "prefix",
+               Status:      "Enabled",
+               Transitions: []LifecycleTransition{transition},
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, NotNil)
+
+       transition = LifecycleTransition{
+               Days:         30,
+               StorageClass: StorageStandard,
+       }
+       rule = LifecycleRule{
+               ID:          "ruleID",
+               Prefix:      "prefix",
+               Status:      "Enabled",
+               Transitions: []LifecycleTransition{transition},
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       transition = LifecycleTransition{
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+               StorageClass:      StorageStandard,
+       }
+       rule = LifecycleRule{
+               ID:          "ruleID",
+               Prefix:      "prefix",
+               Status:      "Enabled",
+               Transitions: []LifecycleTransition{transition},
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       transition1 := LifecycleTransition{
+               Days:         30,
+               StorageClass: StorageIA,
+       }
+       transition2 := LifecycleTransition{
+               Days:         60,
+               StorageClass: StorageArchive,
+       }
+       transition3 := LifecycleTransition{
+               Days:         100,
+               StorageClass: StorageArchive,
+       }
+       rule = LifecycleRule{
+               ID:          "ruleID",
+               Prefix:      "prefix",
+               Status:      "Enabled",
+               Transitions: []LifecycleTransition{transition1, transition2, transition3},
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       rule = LifecycleRule{
+               ID:     "ruleID",
+               Prefix: "prefix",
+               Status: "Enabled",
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       rules = []LifecycleRule{}
+       err1 := verifyLifecycleRules(rules)
+       c.Assert(err1, NotNil)
+
+       expiration = LifecycleExpiration{
+               Days: 30,
+       }
+       rule = LifecycleRule{
+               ID:         "ruleID",
+               Prefix:     "prefix",
+               Status:     "Enabled",
+               Expiration: &expiration,
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       expiration = LifecycleExpiration{
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+       }
+       rule = LifecycleRule{
+               ID:         "ruleID",
+               Prefix:     "prefix",
+               Status:     "Enabled",
+               Expiration: &expiration,
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       abortMPU = LifecycleAbortMultipartUpload{
+               Days: 30,
+       }
+       rule = LifecycleRule{
+               ID:                   "ruleID",
+               Prefix:               "prefix",
+               Status:               "Enabled",
+               AbortMultipartUpload: &abortMPU,
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       abortMPU = LifecycleAbortMultipartUpload{
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+       }
+       rule = LifecycleRule{
+               ID:                   "ruleID",
+               Prefix:               "prefix",
+               Status:               "Enabled",
+               AbortMultipartUpload: &abortMPU,
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       expiration = LifecycleExpiration{
+               Days: 30,
+       }
+       abortMPU = LifecycleAbortMultipartUpload{
+               Days: 30,
+       }
+       rule = LifecycleRule{
+               ID:                   "ruleID",
+               Prefix:               "prefix",
+               Status:               "Enabled",
+               Expiration:           &expiration,
+               AbortMultipartUpload: &abortMPU,
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       expiration = LifecycleExpiration{
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+       }
+       abortMPU = LifecycleAbortMultipartUpload{
+               Days: 30,
+       }
+       transition = LifecycleTransition{
+               Days:         30,
+               StorageClass: StorageIA,
+       }
+       rule = LifecycleRule{
+               ID:                   "ruleID",
+               Prefix:               "prefix",
+               Status:               "Enabled",
+               Expiration:           &expiration,
+               AbortMultipartUpload: &abortMPU,
+               Transitions:          []LifecycleTransition{transition},
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+
+       expiration = LifecycleExpiration{
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+       }
+       abortMPU = LifecycleAbortMultipartUpload{
+               Days: 30,
+       }
+       transition1 = LifecycleTransition{
+               Days:         30,
+               StorageClass: StorageIA,
+       }
+       transition2 = LifecycleTransition{
+               Days:         60,
+               StorageClass: StorageArchive,
+       }
+       rule = LifecycleRule{
+               ID:                   "ruleID",
+               Prefix:               "prefix",
+               Status:               "Enabled",
+               Expiration:           &expiration,
+               AbortMultipartUpload: &abortMPU,
+               Transitions:          []LifecycleTransition{transition1, transition2},
+       }
+       rules = []LifecycleRule{rule}
+       err = verifyLifecycleRules(rules)
+       c.Assert(err, IsNil)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload.go
new file mode 100644 (file)
index 0000000..8b3ea09
--- /dev/null
@@ -0,0 +1,552 @@
+package oss
+
+import (
+       "crypto/md5"
+       "encoding/base64"
+       "encoding/hex"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "io/ioutil"
+       "net/http"
+       "os"
+       "path/filepath"
+       "time"
+)
+
+// UploadFile is multipart file upload.
+//
+// objectKey    the object name.
+// filePath    the local file path to upload.
+// partSize    the part size in byte.
+// options    the options for uploading object.
+//
+// error    it's nil if the operation succeeds, otherwise it's an error object.
+//
+func (bucket Bucket) UploadFile(objectKey, filePath string, partSize int64, options ...Option) error {
+       if partSize < MinPartSize || partSize > MaxPartSize {
+               return errors.New("oss: part size invalid range (100KB, 5GB]")
+       }
+
+       cpConf := getCpConfig(options)
+       routines := getRoutines(options)
+
+       if cpConf != nil && cpConf.IsEnable {
+               cpFilePath := getUploadCpFilePath(cpConf, filePath, bucket.BucketName, objectKey)
+               if cpFilePath != "" {
+                       return bucket.uploadFileWithCp(objectKey, filePath, partSize, options, cpFilePath, routines)
+               }
+       }
+
+       return bucket.uploadFile(objectKey, filePath, partSize, options, routines)
+}
+
+func getUploadCpFilePath(cpConf *cpConfig, srcFile, destBucket, destObject string) string {
+       if cpConf.FilePath == "" && cpConf.DirPath != "" {
+               dest := fmt.Sprintf("oss://%v/%v", destBucket, destObject)
+               absPath, _ := filepath.Abs(srcFile)
+               cpFileName := getCpFileName(absPath, dest, "")
+               cpConf.FilePath = cpConf.DirPath + string(os.PathSeparator) + cpFileName
+       }
+       return cpConf.FilePath
+}
+
+// ----- concurrent upload without checkpoint  -----
+
+// getCpConfig gets checkpoint configuration
+func getCpConfig(options []Option) *cpConfig {
+       cpcOpt, err := FindOption(options, checkpointConfig, nil)
+       if err != nil || cpcOpt == nil {
+               return nil
+       }
+
+       return cpcOpt.(*cpConfig)
+}
+
+// getCpFileName return the name of the checkpoint file
+func getCpFileName(src, dest, versionId string) string {
+       md5Ctx := md5.New()
+       md5Ctx.Write([]byte(src))
+       srcCheckSum := hex.EncodeToString(md5Ctx.Sum(nil))
+
+       md5Ctx.Reset()
+       md5Ctx.Write([]byte(dest))
+       destCheckSum := hex.EncodeToString(md5Ctx.Sum(nil))
+
+       if versionId == "" {
+               return fmt.Sprintf("%v-%v.cp", srcCheckSum, destCheckSum)
+       }
+
+       md5Ctx.Reset()
+       md5Ctx.Write([]byte(versionId))
+       versionCheckSum := hex.EncodeToString(md5Ctx.Sum(nil))
+       return fmt.Sprintf("%v-%v-%v.cp", srcCheckSum, destCheckSum, versionCheckSum)
+}
+
+// getRoutines gets the routine count. by default it's 1.
+func getRoutines(options []Option) int {
+       rtnOpt, err := FindOption(options, routineNum, nil)
+       if err != nil || rtnOpt == nil {
+               return 1
+       }
+
+       rs := rtnOpt.(int)
+       if rs < 1 {
+               rs = 1
+       } else if rs > 100 {
+               rs = 100
+       }
+
+       return rs
+}
+
+// getPayer return the payer of the request
+func getPayer(options []Option) string {
+       payerOpt, err := FindOption(options, HTTPHeaderOssRequester, nil)
+       if err != nil || payerOpt == nil {
+               return ""
+       }
+       return payerOpt.(string)
+}
+
+// GetProgressListener gets the progress callback
+func GetProgressListener(options []Option) ProgressListener {
+       isSet, listener, _ := IsOptionSet(options, progressListener)
+       if !isSet {
+               return nil
+       }
+       return listener.(ProgressListener)
+}
+
+// uploadPartHook is for testing usage
+type uploadPartHook func(id int, chunk FileChunk) error
+
+var uploadPartHooker uploadPartHook = defaultUploadPart
+
+func defaultUploadPart(id int, chunk FileChunk) error {
+       return nil
+}
+
+// workerArg defines worker argument structure
+type workerArg struct {
+       bucket   *Bucket
+       filePath string
+       imur     InitiateMultipartUploadResult
+       options  []Option
+       hook     uploadPartHook
+}
+
+// worker is the worker coroutine function
+type defaultUploadProgressListener struct {
+}
+
+// ProgressChanged no-ops
+func (listener *defaultUploadProgressListener) ProgressChanged(event *ProgressEvent) {
+}
+
+func worker(id int, arg workerArg, jobs <-chan FileChunk, results chan<- UploadPart, failed chan<- error, die <-chan bool) {
+       for chunk := range jobs {
+               if err := arg.hook(id, chunk); err != nil {
+                       failed <- err
+                       break
+               }
+               var respHeader http.Header
+               p := Progress(&defaultUploadProgressListener{})
+               opts := make([]Option, len(arg.options)+2)
+               opts = append(opts, arg.options...)
+
+               // use defaultUploadProgressListener
+               opts = append(opts, p, GetResponseHeader(&respHeader))
+
+               startT := time.Now().UnixNano() / 1000 / 1000 / 1000
+               part, err := arg.bucket.UploadPartFromFile(arg.imur, arg.filePath, chunk.Offset, chunk.Size, chunk.Number, opts...)
+               endT := time.Now().UnixNano() / 1000 / 1000 / 1000
+               if err != nil {
+                       arg.bucket.Client.Config.WriteLog(Debug, "upload part error,cost:%d second,part number:%d,request id:%s,error:%s\n", endT-startT, chunk.Number, GetRequestId(respHeader), err.Error())
+                       failed <- err
+                       break
+               }
+               select {
+               case <-die:
+                       return
+               default:
+               }
+               results <- part
+       }
+}
+
+// scheduler function
+func scheduler(jobs chan FileChunk, chunks []FileChunk) {
+       for _, chunk := range chunks {
+               jobs <- chunk
+       }
+       close(jobs)
+}
+
+func getTotalBytes(chunks []FileChunk) int64 {
+       var tb int64
+       for _, chunk := range chunks {
+               tb += chunk.Size
+       }
+       return tb
+}
+
+// uploadFile is a concurrent upload, without checkpoint
+func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, options []Option, routines int) error {
+       listener := GetProgressListener(options)
+
+       chunks, err := SplitFileByPartSize(filePath, partSize)
+       if err != nil {
+               return err
+       }
+
+       partOptions := ChoiceTransferPartOption(options)
+       completeOptions := ChoiceCompletePartOption(options)
+       abortOptions := ChoiceAbortPartOption(options)
+
+       // Initialize the multipart upload
+       imur, err := bucket.InitiateMultipartUpload(objectKey, options...)
+       if err != nil {
+               return err
+       }
+
+       jobs := make(chan FileChunk, len(chunks))
+       results := make(chan UploadPart, len(chunks))
+       failed := make(chan error)
+       die := make(chan bool)
+
+       var completedBytes int64
+       totalBytes := getTotalBytes(chunks)
+       event := newProgressEvent(TransferStartedEvent, 0, totalBytes, 0)
+       publishProgress(listener, event)
+
+       // Start the worker coroutine
+       arg := workerArg{&bucket, filePath, imur, partOptions, uploadPartHooker}
+       for w := 1; w <= routines; w++ {
+               go worker(w, arg, jobs, results, failed, die)
+       }
+
+       // Schedule the jobs
+       go scheduler(jobs, chunks)
+
+       // Waiting for the upload finished
+       completed := 0
+       parts := make([]UploadPart, len(chunks))
+       for completed < len(chunks) {
+               select {
+               case part := <-results:
+                       completed++
+                       parts[part.PartNumber-1] = part
+                       completedBytes += chunks[part.PartNumber-1].Size
+
+                       // why RwBytes in ProgressEvent is 0 ?
+                       // because read or write event has been notified in teeReader.Read()
+                       event = newProgressEvent(TransferDataEvent, completedBytes, totalBytes, chunks[part.PartNumber-1].Size)
+                       publishProgress(listener, event)
+               case err := <-failed:
+                       close(die)
+                       event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes, 0)
+                       publishProgress(listener, event)
+                       bucket.AbortMultipartUpload(imur, abortOptions...)
+                       return err
+               }
+
+               if completed >= len(chunks) {
+                       break
+               }
+       }
+
+       event = newProgressEvent(TransferStartedEvent, completedBytes, totalBytes, 0)
+       publishProgress(listener, event)
+
+       // Complete the multpart upload
+       _, err = bucket.CompleteMultipartUpload(imur, parts, completeOptions...)
+       if err != nil {
+               bucket.AbortMultipartUpload(imur, abortOptions...)
+               return err
+       }
+       return nil
+}
+
+// ----- concurrent upload with checkpoint  -----
+const uploadCpMagic = "FE8BB4EA-B593-4FAC-AD7A-2459A36E2E62"
+
+type uploadCheckpoint struct {
+       Magic     string   // Magic
+       MD5       string   // Checkpoint file content's MD5
+       FilePath  string   // Local file path
+       FileStat  cpStat   // File state
+       ObjectKey string   // Key
+       UploadID  string   // Upload ID
+       Parts     []cpPart // All parts of the local file
+}
+
+type cpStat struct {
+       Size         int64     // File size
+       LastModified time.Time // File's last modified time
+       MD5          string    // Local file's MD5
+}
+
+type cpPart struct {
+       Chunk       FileChunk  // File chunk
+       Part        UploadPart // Uploaded part
+       IsCompleted bool       // Upload complete flag
+}
+
+// isValid checks if the uploaded data is valid---it's valid when the file is not updated and the checkpoint data is valid.
+func (cp uploadCheckpoint) isValid(filePath string) (bool, error) {
+       // Compare the CP's magic number and MD5.
+       cpb := cp
+       cpb.MD5 = ""
+       js, _ := json.Marshal(cpb)
+       sum := md5.Sum(js)
+       b64 := base64.StdEncoding.EncodeToString(sum[:])
+
+       if cp.Magic != uploadCpMagic || b64 != cp.MD5 {
+               return false, nil
+       }
+
+       // Make sure if the local file is updated.
+       fd, err := os.Open(filePath)
+       if err != nil {
+               return false, err
+       }
+       defer fd.Close()
+
+       st, err := fd.Stat()
+       if err != nil {
+               return false, err
+       }
+
+       md, err := calcFileMD5(filePath)
+       if err != nil {
+               return false, err
+       }
+
+       // Compare the file size, file's last modified time and file's MD5
+       if cp.FileStat.Size != st.Size() ||
+               !cp.FileStat.LastModified.Equal(st.ModTime()) ||
+               cp.FileStat.MD5 != md {
+               return false, nil
+       }
+
+       return true, nil
+}
+
+// load loads from the file
+func (cp *uploadCheckpoint) load(filePath string) error {
+       contents, err := ioutil.ReadFile(filePath)
+       if err != nil {
+               return err
+       }
+
+       err = json.Unmarshal(contents, cp)
+       return err
+}
+
+// dump dumps to the local file
+func (cp *uploadCheckpoint) dump(filePath string) error {
+       bcp := *cp
+
+       // Calculate MD5
+       bcp.MD5 = ""
+       js, err := json.Marshal(bcp)
+       if err != nil {
+               return err
+       }
+       sum := md5.Sum(js)
+       b64 := base64.StdEncoding.EncodeToString(sum[:])
+       bcp.MD5 = b64
+
+       // Serialization
+       js, err = json.Marshal(bcp)
+       if err != nil {
+               return err
+       }
+
+       // Dump
+       return ioutil.WriteFile(filePath, js, FilePermMode)
+}
+
+// updatePart updates the part status
+func (cp *uploadCheckpoint) updatePart(part UploadPart) {
+       cp.Parts[part.PartNumber-1].Part = part
+       cp.Parts[part.PartNumber-1].IsCompleted = true
+}
+
+// todoParts returns unfinished parts
+func (cp *uploadCheckpoint) todoParts() []FileChunk {
+       fcs := []FileChunk{}
+       for _, part := range cp.Parts {
+               if !part.IsCompleted {
+                       fcs = append(fcs, part.Chunk)
+               }
+       }
+       return fcs
+}
+
+// allParts returns all parts
+func (cp *uploadCheckpoint) allParts() []UploadPart {
+       ps := []UploadPart{}
+       for _, part := range cp.Parts {
+               ps = append(ps, part.Part)
+       }
+       return ps
+}
+
+// getCompletedBytes returns completed bytes count
+func (cp *uploadCheckpoint) getCompletedBytes() int64 {
+       var completedBytes int64
+       for _, part := range cp.Parts {
+               if part.IsCompleted {
+                       completedBytes += part.Chunk.Size
+               }
+       }
+       return completedBytes
+}
+
+// calcFileMD5 calculates the MD5 for the specified local file
+func calcFileMD5(filePath string) (string, error) {
+       return "", nil
+}
+
+// prepare initializes the multipart upload
+func prepare(cp *uploadCheckpoint, objectKey, filePath string, partSize int64, bucket *Bucket, options []Option) error {
+       // CP
+       cp.Magic = uploadCpMagic
+       cp.FilePath = filePath
+       cp.ObjectKey = objectKey
+
+       // Local file
+       fd, err := os.Open(filePath)
+       if err != nil {
+               return err
+       }
+       defer fd.Close()
+
+       st, err := fd.Stat()
+       if err != nil {
+               return err
+       }
+       cp.FileStat.Size = st.Size()
+       cp.FileStat.LastModified = st.ModTime()
+       md, err := calcFileMD5(filePath)
+       if err != nil {
+               return err
+       }
+       cp.FileStat.MD5 = md
+
+       // Chunks
+       parts, err := SplitFileByPartSize(filePath, partSize)
+       if err != nil {
+               return err
+       }
+
+       cp.Parts = make([]cpPart, len(parts))
+       for i, part := range parts {
+               cp.Parts[i].Chunk = part
+               cp.Parts[i].IsCompleted = false
+       }
+
+       // Init load
+       imur, err := bucket.InitiateMultipartUpload(objectKey, options...)
+       if err != nil {
+               return err
+       }
+       cp.UploadID = imur.UploadID
+
+       return nil
+}
+
+// complete completes the multipart upload and deletes the local CP files
+func complete(cp *uploadCheckpoint, bucket *Bucket, parts []UploadPart, cpFilePath string, options []Option) error {
+       imur := InitiateMultipartUploadResult{Bucket: bucket.BucketName,
+               Key: cp.ObjectKey, UploadID: cp.UploadID}
+       _, err := bucket.CompleteMultipartUpload(imur, parts, options...)
+       if err != nil {
+               return err
+       }
+       os.Remove(cpFilePath)
+       return err
+}
+
+// uploadFileWithCp handles concurrent upload with checkpoint
+func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64, options []Option, cpFilePath string, routines int) error {
+       listener := GetProgressListener(options)
+
+       partOptions := ChoiceTransferPartOption(options)
+       completeOptions := ChoiceCompletePartOption(options)
+
+       // Load CP data
+       ucp := uploadCheckpoint{}
+       err := ucp.load(cpFilePath)
+       if err != nil {
+               os.Remove(cpFilePath)
+       }
+
+       // Load error or the CP data is invalid.
+       valid, err := ucp.isValid(filePath)
+       if err != nil || !valid {
+               if err = prepare(&ucp, objectKey, filePath, partSize, &bucket, options); err != nil {
+                       return err
+               }
+               os.Remove(cpFilePath)
+       }
+
+       chunks := ucp.todoParts()
+       imur := InitiateMultipartUploadResult{
+               Bucket:   bucket.BucketName,
+               Key:      objectKey,
+               UploadID: ucp.UploadID}
+
+       jobs := make(chan FileChunk, len(chunks))
+       results := make(chan UploadPart, len(chunks))
+       failed := make(chan error)
+       die := make(chan bool)
+
+       completedBytes := ucp.getCompletedBytes()
+
+       // why RwBytes in ProgressEvent is 0 ?
+       // because read or write event has been notified in teeReader.Read()
+       event := newProgressEvent(TransferStartedEvent, completedBytes, ucp.FileStat.Size, 0)
+       publishProgress(listener, event)
+
+       // Start the workers
+       arg := workerArg{&bucket, filePath, imur, partOptions, uploadPartHooker}
+       for w := 1; w <= routines; w++ {
+               go worker(w, arg, jobs, results, failed, die)
+       }
+
+       // Schedule jobs
+       go scheduler(jobs, chunks)
+
+       // Waiting for the job finished
+       completed := 0
+       for completed < len(chunks) {
+               select {
+               case part := <-results:
+                       completed++
+                       ucp.updatePart(part)
+                       ucp.dump(cpFilePath)
+                       completedBytes += ucp.Parts[part.PartNumber-1].Chunk.Size
+                       event = newProgressEvent(TransferDataEvent, completedBytes, ucp.FileStat.Size, ucp.Parts[part.PartNumber-1].Chunk.Size)
+                       publishProgress(listener, event)
+               case err := <-failed:
+                       close(die)
+                       event = newProgressEvent(TransferFailedEvent, completedBytes, ucp.FileStat.Size, 0)
+                       publishProgress(listener, event)
+                       return err
+               }
+
+               if completed >= len(chunks) {
+                       break
+               }
+       }
+
+       event = newProgressEvent(TransferCompletedEvent, completedBytes, ucp.FileStat.Size, 0)
+       publishProgress(listener, event)
+
+       // Complete the multipart upload
+       err = complete(&ucp, &bucket, ucp.allParts(), cpFilePath, completeOptions)
+       return err
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload_test.go
new file mode 100644 (file)
index 0000000..7368949
--- /dev/null
@@ -0,0 +1,741 @@
+package oss
+
+import (
+       "fmt"
+       "io"
+       "net/http"
+       "os"
+       "time"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssUploadSuite struct {
+       client *Client
+       bucket *Bucket
+}
+
+var _ = Suite(&OssUploadSuite{})
+
+// SetUpSuite runs once when the suite starts running
+func (s *OssUploadSuite) SetUpSuite(c *C) {
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+       s.client = client
+
+       s.client.CreateBucket(bucketName)
+
+       bucket, err := s.client.Bucket(bucketName)
+       c.Assert(err, IsNil)
+       s.bucket = bucket
+
+       testLogger.Println("test upload started")
+}
+
+// TearDownSuite runs before each test or benchmark starts running
+func (s *OssUploadSuite) TearDownSuite(c *C) {
+       // Delete part
+       keyMarker := KeyMarker("")
+       uploadIDMarker := UploadIDMarker("")
+       for {
+               lmur, err := s.bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+               c.Assert(err, IsNil)
+               for _, upload := range lmur.Uploads {
+                       var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName,
+                               Key: upload.Key, UploadID: upload.UploadID}
+                       err = s.bucket.AbortMultipartUpload(imur)
+                       c.Assert(err, IsNil)
+               }
+               keyMarker = KeyMarker(lmur.NextKeyMarker)
+               uploadIDMarker = UploadIDMarker(lmur.NextUploadIDMarker)
+               if !lmur.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete objects
+       marker := Marker("")
+       for {
+               lor, err := s.bucket.ListObjects(marker)
+               c.Assert(err, IsNil)
+               for _, object := range lor.Objects {
+                       err = s.bucket.DeleteObject(object.Key)
+                       c.Assert(err, IsNil)
+               }
+               marker = Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete bucket
+       err := s.client.DeleteBucket(s.bucket.BucketName)
+       c.Assert(err, IsNil)
+
+       testLogger.Println("test upload completed")
+}
+
+// SetUpTest runs after each test or benchmark runs
+func (s *OssUploadSuite) SetUpTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+}
+
+// TearDownTest runs once after all tests or benchmarks have finished running
+func (s *OssUploadSuite) TearDownTest(c *C) {
+       err := removeTempFiles("../oss", ".jpg")
+       c.Assert(err, IsNil)
+}
+
+// TestUploadRoutineWithoutRecovery tests multiroutineed upload without checkpoint
+func (s *OssUploadSuite) TestUploadRoutineWithoutRecovery(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       // Routines is not specified, by default single routine
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024)
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Specify routine count as 1
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(1))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Specify routine count as 3, which is smaller than parts count 5
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Specify routine count as 5, which is same as the part count 5
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(5))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Specify routine count as 10, which is bigger than the part count 5.
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(10))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Invalid routine count, it will use 1 automatically.
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(0))
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Invalid routine count, it will use 1 automatically
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(-1))
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Option
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Meta("myprop", "mypropval"))
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// ErrorHooker is a UploadPart hook---it will fail the 5th part's upload.
+func ErrorHooker(id int, chunk FileChunk) error {
+       if chunk.Number == 5 {
+               time.Sleep(time.Second)
+               return fmt.Errorf("ErrorHooker")
+       }
+       return nil
+}
+
+// TestUploadRoutineWithoutRecoveryNegative is multiroutineed upload without checkpoint
+func (s *OssUploadSuite) TestUploadRoutineWithoutRecoveryNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+
+       uploadPartHooker = ErrorHooker
+       // Worker routine error
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(2))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       uploadPartHooker = defaultUploadPart
+
+       // Local file does not exist
+       err = s.bucket.UploadFile(objectName, "NotExist", 100*1024, Routines(2))
+       c.Assert(err, NotNil)
+
+       // The part size is invalid
+       err = s.bucket.UploadFile(objectName, fileName, 1024, Routines(2))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.UploadFile(objectName, fileName, 1024*1024*1024*100, Routines(2))
+       c.Assert(err, NotNil)
+}
+
+// TestUploadRoutineWithRecovery is multi-routine upload with resumable recovery
+func (s *OssUploadSuite) TestUploadRoutineWithRecovery(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       newFile := "upload-new-file-2.jpg"
+
+       // Use default routines and default CP file path (fileName+.cp)
+       // First upload for 4 parts
+       uploadPartHooker = ErrorHooker
+       err := s.bucket.UploadFile(objectName, fileName, 100*1024, Checkpoint(true, fileName+".cp"))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       uploadPartHooker = defaultUploadPart
+
+       // Check CP
+       ucp := uploadCheckpoint{}
+       err = ucp.load(fileName + ".cp")
+       c.Assert(err, IsNil)
+       c.Assert(ucp.Magic, Equals, uploadCpMagic)
+       c.Assert(len(ucp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ=="))
+       c.Assert(ucp.FilePath, Equals, fileName)
+       c.Assert(ucp.FileStat.Size, Equals, int64(482048))
+       c.Assert(len(ucp.FileStat.LastModified.String()) > 0, Equals, true)
+       c.Assert(ucp.FileStat.MD5, Equals, "")
+       c.Assert(ucp.ObjectKey, Equals, objectName)
+       c.Assert(len(ucp.UploadID), Equals, len("3F79722737D1469980DACEDCA325BB52"))
+       c.Assert(len(ucp.Parts), Equals, 5)
+       c.Assert(len(ucp.todoParts()), Equals, 1)
+       c.Assert(len(ucp.allParts()), Equals, 5)
+
+       // Second upload, finish the remaining part
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Checkpoint(true, fileName+".cp"))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       err = ucp.load(fileName + ".cp")
+       c.Assert(err, NotNil)
+
+       // Resumable upload with empty checkpoint path
+       uploadPartHooker = ErrorHooker
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), CheckpointDir(true, ""))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       uploadPartHooker = defaultUploadPart
+       ucp = uploadCheckpoint{}
+       err = ucp.load(fileName + ".cp")
+       c.Assert(err, NotNil)
+
+       // Resumable upload with checkpoint dir
+       uploadPartHooker = ErrorHooker
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), CheckpointDir(true, "./"))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       uploadPartHooker = defaultUploadPart
+
+       // Check CP
+       ucp = uploadCheckpoint{}
+       cpConf := cpConfig{IsEnable: true, DirPath: "./"}
+       cpFilePath := getUploadCpFilePath(&cpConf, fileName, s.bucket.BucketName, objectName)
+       err = ucp.load(cpFilePath)
+       c.Assert(err, IsNil)
+       c.Assert(ucp.Magic, Equals, uploadCpMagic)
+       c.Assert(len(ucp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ=="))
+       c.Assert(ucp.FilePath, Equals, fileName)
+       c.Assert(ucp.FileStat.Size, Equals, int64(482048))
+       c.Assert(len(ucp.FileStat.LastModified.String()) > 0, Equals, true)
+       c.Assert(ucp.FileStat.MD5, Equals, "")
+       c.Assert(ucp.ObjectKey, Equals, objectName)
+       c.Assert(len(ucp.UploadID), Equals, len("3F79722737D1469980DACEDCA325BB52"))
+       c.Assert(len(ucp.Parts), Equals, 5)
+       c.Assert(len(ucp.todoParts()), Equals, 1)
+       c.Assert(len(ucp.allParts()), Equals, 5)
+
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), CheckpointDir(true, "./"))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       err = ucp.load(cpFilePath)
+       c.Assert(err, NotNil)
+
+       // Upload all 5 parts without error
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, objectName+".cp"))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Upload all 5 parts with 10 routines without error
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(10), Checkpoint(true, objectName+".cp"))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+
+       // Option
+       err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, objectName+".cp"), Meta("myprop", "mypropval"))
+
+       meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+       c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err = compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestUploadRoutineWithRecoveryNegative is multiroutineed upload without checkpoint
+func (s *OssUploadSuite) TestUploadRoutineWithRecoveryNegative(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+
+       // The local file does not exist
+       err := s.bucket.UploadFile(objectName, "NotExist", 100*1024, Checkpoint(true, "NotExist.cp"))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.UploadFile(objectName, "NotExist", 100*1024, Routines(2), Checkpoint(true, "NotExist.cp"))
+       c.Assert(err, NotNil)
+
+       // Specified part size is invalid
+       err = s.bucket.UploadFile(objectName, fileName, 1024, Checkpoint(true, fileName+".cp"))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.UploadFile(objectName, fileName, 1024, Routines(2), Checkpoint(true, fileName+".cp"))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.UploadFile(objectName, fileName, 1024*1024*1024*100, Checkpoint(true, fileName+".cp"))
+       c.Assert(err, NotNil)
+
+       err = s.bucket.UploadFile(objectName, fileName, 1024*1024*1024*100, Routines(2), Checkpoint(true, fileName+".cp"))
+       c.Assert(err, NotNil)
+}
+
+// TestUploadLocalFileChange tests the file is updated while being uploaded
+func (s *OssUploadSuite) TestUploadLocalFileChange(c *C) {
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       localFile := RandStr(8) + ".jpg"
+       newFile := RandStr(8) + ".jpg"
+
+       os.Remove(localFile)
+       err := copyFile(fileName, localFile)
+       c.Assert(err, IsNil)
+
+       // First upload for 4 parts
+       uploadPartHooker = ErrorHooker
+       err = s.bucket.UploadFile(objectName, localFile, 100*1024, Checkpoint(true, localFile+".cp"))
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       uploadPartHooker = defaultUploadPart
+
+       os.Remove(localFile)
+       err = copyFile(fileName, localFile)
+       c.Assert(err, IsNil)
+
+       // Updating the file. The second upload will re-upload all 5 parts.
+       err = s.bucket.UploadFile(objectName, localFile, 100*1024, Checkpoint(true, localFile+".cp"))
+       c.Assert(err, IsNil)
+
+       os.Remove(newFile)
+       err = s.bucket.GetObjectToFile(objectName, newFile)
+       c.Assert(err, IsNil)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       err = s.bucket.DeleteObject(objectName)
+       c.Assert(err, IsNil)
+}
+
+// TestUploadPartArchiveObject
+func (s *OssUploadSuite) TestUploadPartArchiveObject(c *C) {
+       // create archive bucket
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName, StorageClass(StorageArchive))
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+       objectName := objectNamePrefix + RandStr(8)
+
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       fileInfo, err := os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       // Updating the file,archive object
+       err = bucket.UploadFile(objectName, fileName, fileInfo.Size()/2, ObjectStorageClass(StorageArchive))
+       c.Assert(err, IsNil)
+
+       // Updating the file,archive object,checkpoint
+       err = bucket.UploadFile(objectName, fileName, fileInfo.Size()/2, ObjectStorageClass(StorageArchive), Checkpoint(true, fileName+".cp"))
+       c.Assert(err, IsNil)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+func copyFile(src, dst string) error {
+       srcFile, err := os.Open(src)
+       if err != nil {
+               return err
+       }
+       defer srcFile.Close()
+
+       dstFile, err := os.Create(dst)
+       if err != nil {
+               return err
+       }
+       defer dstFile.Close()
+
+       _, err = io.Copy(dstFile, srcFile)
+       return err
+}
+
+func (s *OssUploadSuite) TestVersioningUploadRoutineWithRecovery(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+
+       bucket, err := client.Bucket(bucketName)
+
+       // put bucket version:enabled
+       var versioningConfig VersioningConfig
+       versioningConfig.Status = string(VersionEnabled)
+       err = client.SetBucketVersioning(bucketName, versioningConfig)
+       c.Assert(err, IsNil)
+
+       // begin test
+       objectName := objectNamePrefix + RandStr(8)
+       fileName := "test-file-" + RandStr(8)
+       fileData := RandStr(500 * 1024)
+       CreateFile(fileName, fileData, c)
+       newFile := "test-file-" + RandStr(8)
+
+       // Use default routines and default CP file path (fileName+.cp)Header
+       // First upload for 4 parts
+       var respHeader http.Header
+       uploadPartHooker = ErrorHooker
+       options := []Option{Checkpoint(true, fileName+".cp"), GetResponseHeader(&respHeader)}
+       err = bucket.UploadFile(objectName, fileName, 100*1024, options...)
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "ErrorHooker")
+       c.Assert(GetVersionId(respHeader), Equals, "")
+
+       uploadPartHooker = defaultUploadPart
+
+       // Second upload, finish the remaining part
+       options = []Option{Checkpoint(true, fileName+".cp"), GetResponseHeader(&respHeader)}
+       err = bucket.UploadFile(objectName, fileName, 100*1024, options...)
+       c.Assert(err, IsNil)
+       versionIdUp := GetVersionId(respHeader)
+       c.Assert(len(versionIdUp) > 0, Equals, true)
+
+       os.Remove(newFile)
+       var respHeaderDown http.Header
+       err = bucket.GetObjectToFile(objectName, newFile, GetResponseHeader(&respHeaderDown))
+       versionIdDown := GetVersionId(respHeaderDown)
+       c.Assert(err, IsNil)
+       c.Assert(versionIdUp, Equals, versionIdDown)
+
+       eq, err := compareFiles(fileName, newFile)
+       c.Assert(err, IsNil)
+       c.Assert(eq, Equals, true)
+
+       os.Remove(fileName)
+       os.Remove(newFile)
+       bucket.DeleteObject(objectName)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// TestUploadFileChoiceOptions
+func (s *OssUploadSuite) TestUploadFileChoiceOptions(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       fileInfo, err := os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+
+       // UploadFile with properties
+       options := []Option{
+               ObjectACL(ACLPublicRead),
+               RequestPayer(Requester),
+               TrafficLimitHeader(1024 * 1024 * 8),
+               ServerSideEncryption("AES256"),
+               ObjectStorageClass(StorageArchive),
+       }
+
+       // Updating the file
+       err = bucket.UploadFile(objectName, fileName, fileInfo.Size()/2, options...)
+       c.Assert(err, IsNil)
+
+       // GetMetaDetail
+       headerResp, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+
+       c.Assert(headerResp.Get("X-Oss-Server-Side-Encryption"), Equals, "AES256")
+       aclResult, err := bucket.GetObjectACL(objectName)
+       c.Assert(aclResult.ACL, Equals, "public-read")
+       c.Assert(err, IsNil)
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// TestUploadFileWithCpChoiceOptions
+func (s *OssUploadSuite) TestUploadFileWithCpChoiceOptions(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       fileInfo, err := os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+
+       // UploadFile with properties
+       options := []Option{
+               ObjectACL(ACLPublicRead),
+               RequestPayer(Requester),
+               TrafficLimitHeader(1024 * 1024 * 8),
+               ServerSideEncryption("AES256"),
+               ObjectStorageClass(StorageArchive),
+               Checkpoint(true, fileName+".cp"), // with checkpoint
+       }
+
+       // Updating the file
+       err = bucket.UploadFile(objectName, fileName, fileInfo.Size()/2, options...)
+       c.Assert(err, IsNil)
+
+       // GetMetaDetail
+       headerResp, err := bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+
+       c.Assert(headerResp.Get("X-Oss-Server-Side-Encryption"), Equals, "AES256")
+       c.Assert(headerResp.Get("X-Oss-Storage-Class"), Equals, "Archive")
+
+       aclResult, err := bucket.GetObjectACL(objectName)
+       c.Assert(aclResult.ACL, Equals, "public-read")
+       c.Assert(err, IsNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// TestUploadFileWithForbidOverWrite
+func (s *OssUploadSuite) TestUploadFileWithForbidOverWrite(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       fileInfo, err := os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+
+       // UploadFile with properties
+       options := []Option{
+               ObjectACL(ACLPublicRead),
+               RequestPayer(Requester),
+               TrafficLimitHeader(1024 * 1024 * 8),
+               ServerSideEncryption("AES256"),
+               ObjectStorageClass(StorageArchive),
+               ForbidOverWrite(true),
+               Checkpoint(true, fileName+".cp"),
+       }
+
+       // Updating the file
+       err = bucket.UploadFile(objectName, fileName, fileInfo.Size()/2, options...)
+       c.Assert(err, IsNil)
+
+       // Updating the file with ForbidOverWrite(true)
+       err = bucket.UploadFile(objectName, fileName, fileInfo.Size()/2, options...)
+       c.Assert(err, NotNil)
+
+       // without Checkpoint
+       options = []Option{
+               ObjectACL(ACLPublicRead),
+               RequestPayer(Requester),
+               TrafficLimitHeader(1024 * 1024 * 8),
+               ServerSideEncryption("AES256"),
+               ObjectStorageClass(StorageArchive),
+               ForbidOverWrite(true),
+       }
+
+       // Updating the file with ForbidOverWrite(true)
+       err = bucket.UploadFile(objectName, fileName, fileInfo.Size()/2, options...)
+       c.Assert(err, NotNil)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
+
+// TestUploadFileWithSequential
+func (s *OssUploadSuite) TestUploadFileWithSequential(c *C) {
+       // create a bucket with default proprety
+       client, err := New(endpoint, accessID, accessKey)
+       c.Assert(err, IsNil)
+
+       bucketName := bucketNamePrefix + RandLowStr(6)
+       err = client.CreateBucket(bucketName)
+       c.Assert(err, IsNil)
+       bucket, err := client.Bucket(bucketName)
+
+       fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+       fileInfo, err := os.Stat(fileName)
+       c.Assert(err, IsNil)
+
+       objectName := objectNamePrefix + RandStr(8)
+
+       var respHeader http.Header
+
+       // UploadFile with properties
+       options := []Option{
+               Sequential(),
+               GetResponseHeader(&respHeader),
+               Checkpoint(true, fileName+".cp"),
+       }
+
+       // Updating the file
+       err = bucket.UploadFile(objectName, fileName, fileInfo.Size()/2, options...)
+       c.Assert(err, IsNil)
+
+       respHeader, err = bucket.GetObjectDetailedMeta(objectName)
+       c.Assert(err, IsNil)
+
+       strMD5 := respHeader.Get("Content-MD5")
+       c.Assert(len(strMD5) > 0, Equals, true)
+
+       ForceDeleteBucket(client, bucketName, c)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils.go
new file mode 100644 (file)
index 0000000..3dff335
--- /dev/null
@@ -0,0 +1,512 @@
+package oss
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "hash/crc32"
+       "hash/crc64"
+       "io"
+       "net/http"
+       "os"
+       "os/exec"
+       "runtime"
+       "strconv"
+       "strings"
+       "time"
+)
+
+var sys_name string
+var sys_release string
+var sys_machine string
+
+func init() {
+       sys_name = runtime.GOOS
+       sys_release = "-"
+       sys_machine = runtime.GOARCH
+
+       if out, err := exec.Command("uname", "-s").CombinedOutput(); err == nil {
+               sys_name = string(bytes.TrimSpace(out))
+       }
+       if out, err := exec.Command("uname", "-r").CombinedOutput(); err == nil {
+               sys_release = string(bytes.TrimSpace(out))
+       }
+       if out, err := exec.Command("uname", "-m").CombinedOutput(); err == nil {
+               sys_machine = string(bytes.TrimSpace(out))
+       }
+}
+
+// userAgent gets user agent
+// It has the SDK version information, OS information and GO version
+func userAgent() string {
+       sys := getSysInfo()
+       return fmt.Sprintf("aliyun-sdk-go/%s (%s/%s/%s;%s)", Version, sys.name,
+               sys.release, sys.machine, runtime.Version())
+}
+
+type sysInfo struct {
+       name    string // OS name such as windows/Linux
+       release string // OS version 2.6.32-220.23.2.ali1089.el5.x86_64 etc
+       machine string // CPU type amd64/x86_64
+}
+
+// getSysInfo gets system info
+// gets the OS information and CPU type
+func getSysInfo() sysInfo {
+       return sysInfo{name: sys_name, release: sys_release, machine: sys_machine}
+}
+
+// GetRangeConfig gets the download range from the options.
+func GetRangeConfig(options []Option) (*UnpackedRange, error) {
+       rangeOpt, err := FindOption(options, HTTPHeaderRange, nil)
+       if err != nil || rangeOpt == nil {
+               return nil, err
+       }
+       return ParseRange(rangeOpt.(string))
+}
+
+// UnpackedRange
+type UnpackedRange struct {
+       HasStart bool  // Flag indicates if the start point is specified
+       HasEnd   bool  // Flag indicates if the end point is specified
+       Start    int64 // Start point
+       End      int64 // End point
+}
+
+// InvalidRangeError returns invalid range error
+func InvalidRangeError(r string) error {
+       return fmt.Errorf("InvalidRange %s", r)
+}
+
+func GetRangeString(unpackRange UnpackedRange) string {
+       var strRange string
+       if unpackRange.HasStart && unpackRange.HasEnd {
+               strRange = fmt.Sprintf("%d-%d", unpackRange.Start, unpackRange.End)
+       } else if unpackRange.HasStart {
+               strRange = fmt.Sprintf("%d-", unpackRange.Start)
+       } else if unpackRange.HasEnd {
+               strRange = fmt.Sprintf("-%d", unpackRange.End)
+       }
+       return strRange
+}
+
+// ParseRange parse various styles of range such as bytes=M-N
+func ParseRange(normalizedRange string) (*UnpackedRange, error) {
+       var err error
+       hasStart := false
+       hasEnd := false
+       var start int64
+       var end int64
+
+       // Bytes==M-N or ranges=M-N
+       nrSlice := strings.Split(normalizedRange, "=")
+       if len(nrSlice) != 2 || nrSlice[0] != "bytes" {
+               return nil, InvalidRangeError(normalizedRange)
+       }
+
+       // Bytes=M-N,X-Y
+       rSlice := strings.Split(nrSlice[1], ",")
+       rStr := rSlice[0]
+
+       if strings.HasSuffix(rStr, "-") { // M-
+               startStr := rStr[:len(rStr)-1]
+               start, err = strconv.ParseInt(startStr, 10, 64)
+               if err != nil {
+                       return nil, InvalidRangeError(normalizedRange)
+               }
+               hasStart = true
+       } else if strings.HasPrefix(rStr, "-") { // -N
+               len := rStr[1:]
+               end, err = strconv.ParseInt(len, 10, 64)
+               if err != nil {
+                       return nil, InvalidRangeError(normalizedRange)
+               }
+               if end == 0 { // -0
+                       return nil, InvalidRangeError(normalizedRange)
+               }
+               hasEnd = true
+       } else { // M-N
+               valSlice := strings.Split(rStr, "-")
+               if len(valSlice) != 2 {
+                       return nil, InvalidRangeError(normalizedRange)
+               }
+               start, err = strconv.ParseInt(valSlice[0], 10, 64)
+               if err != nil {
+                       return nil, InvalidRangeError(normalizedRange)
+               }
+               hasStart = true
+               end, err = strconv.ParseInt(valSlice[1], 10, 64)
+               if err != nil {
+                       return nil, InvalidRangeError(normalizedRange)
+               }
+               hasEnd = true
+       }
+
+       return &UnpackedRange{hasStart, hasEnd, start, end}, nil
+}
+
+// AdjustRange returns adjusted range, adjust the range according to the length of the file
+func AdjustRange(ur *UnpackedRange, size int64) (start, end int64) {
+       if ur == nil {
+               return 0, size
+       }
+
+       if ur.HasStart && ur.HasEnd {
+               start = ur.Start
+               end = ur.End + 1
+               if ur.Start < 0 || ur.Start >= size || ur.End > size || ur.Start > ur.End {
+                       start = 0
+                       end = size
+               }
+       } else if ur.HasStart {
+               start = ur.Start
+               end = size
+               if ur.Start < 0 || ur.Start >= size {
+                       start = 0
+               }
+       } else if ur.HasEnd {
+               start = size - ur.End
+               end = size
+               if ur.End < 0 || ur.End > size {
+                       start = 0
+                       end = size
+               }
+       }
+       return
+}
+
+// GetNowSec returns Unix time, the number of seconds elapsed since January 1, 1970 UTC.
+// gets the current time in Unix time, in seconds.
+func GetNowSec() int64 {
+       return time.Now().Unix()
+}
+
+// GetNowNanoSec returns t as a Unix time, the number of nanoseconds elapsed
+// since January 1, 1970 UTC. The result is undefined if the Unix time
+// in nanoseconds cannot be represented by an int64. Note that this
+// means the result of calling UnixNano on the zero Time is undefined.
+// gets the current time in Unix time, in nanoseconds.
+func GetNowNanoSec() int64 {
+       return time.Now().UnixNano()
+}
+
+// GetNowGMT gets the current time in GMT format.
+func GetNowGMT() string {
+       return time.Now().UTC().Format(http.TimeFormat)
+}
+
+// FileChunk is the file chunk definition
+type FileChunk struct {
+       Number int   // Chunk number
+       Offset int64 // Chunk offset
+       Size   int64 // Chunk size.
+}
+
+// SplitFileByPartNum splits big file into parts by the num of parts.
+// Split the file with specified parts count, returns the split result when error is nil.
+func SplitFileByPartNum(fileName string, chunkNum int) ([]FileChunk, error) {
+       if chunkNum <= 0 || chunkNum > 10000 {
+               return nil, errors.New("chunkNum invalid")
+       }
+
+       file, err := os.Open(fileName)
+       if err != nil {
+               return nil, err
+       }
+       defer file.Close()
+
+       stat, err := file.Stat()
+       if err != nil {
+               return nil, err
+       }
+
+       if int64(chunkNum) > stat.Size() {
+               return nil, errors.New("oss: chunkNum invalid")
+       }
+
+       var chunks []FileChunk
+       var chunk = FileChunk{}
+       var chunkN = (int64)(chunkNum)
+       for i := int64(0); i < chunkN; i++ {
+               chunk.Number = int(i + 1)
+               chunk.Offset = i * (stat.Size() / chunkN)
+               if i == chunkN-1 {
+                       chunk.Size = stat.Size()/chunkN + stat.Size()%chunkN
+               } else {
+                       chunk.Size = stat.Size() / chunkN
+               }
+               chunks = append(chunks, chunk)
+       }
+
+       return chunks, nil
+}
+
+// SplitFileByPartSize splits big file into parts by the size of parts.
+// Splits the file by the part size. Returns the FileChunk when error is nil.
+func SplitFileByPartSize(fileName string, chunkSize int64) ([]FileChunk, error) {
+       if chunkSize <= 0 {
+               return nil, errors.New("chunkSize invalid")
+       }
+
+       file, err := os.Open(fileName)
+       if err != nil {
+               return nil, err
+       }
+       defer file.Close()
+
+       stat, err := file.Stat()
+       if err != nil {
+               return nil, err
+       }
+       var chunkN = stat.Size() / chunkSize
+       if chunkN >= 10000 {
+               return nil, errors.New("Too many parts, please increase part size")
+       }
+
+       var chunks []FileChunk
+       var chunk = FileChunk{}
+       for i := int64(0); i < chunkN; i++ {
+               chunk.Number = int(i + 1)
+               chunk.Offset = i * chunkSize
+               chunk.Size = chunkSize
+               chunks = append(chunks, chunk)
+       }
+
+       if stat.Size()%chunkSize > 0 {
+               chunk.Number = len(chunks) + 1
+               chunk.Offset = int64(len(chunks)) * chunkSize
+               chunk.Size = stat.Size() % chunkSize
+               chunks = append(chunks, chunk)
+       }
+
+       return chunks, nil
+}
+
+// GetPartEnd calculates the end position
+func GetPartEnd(begin int64, total int64, per int64) int64 {
+       if begin+per > total {
+               return total - 1
+       }
+       return begin + per - 1
+}
+
+// CrcTable returns the table constructed from the specified polynomial
+var CrcTable = func() *crc64.Table {
+       return crc64.MakeTable(crc64.ECMA)
+}
+
+// CrcTable returns the table constructed from the specified polynomial
+var crc32Table = func() *crc32.Table {
+       return crc32.MakeTable(crc32.IEEE)
+}
+
+// choiceTransferPartOption choices valid option supported by Uploadpart or DownloadPart
+func ChoiceTransferPartOption(options []Option) []Option {
+       var outOption []Option
+
+       listener, _ := FindOption(options, progressListener, nil)
+       if listener != nil {
+               outOption = append(outOption, Progress(listener.(ProgressListener)))
+       }
+
+       payer, _ := FindOption(options, HTTPHeaderOssRequester, nil)
+       if payer != nil {
+               outOption = append(outOption, RequestPayer(PayerType(payer.(string))))
+       }
+
+       versionId, _ := FindOption(options, "versionId", nil)
+       if versionId != nil {
+               outOption = append(outOption, VersionId(versionId.(string)))
+       }
+
+       trafficLimit, _ := FindOption(options, HTTPHeaderOssTrafficLimit, nil)
+       if trafficLimit != nil {
+               speed, _ := strconv.ParseInt(trafficLimit.(string), 10, 64)
+               outOption = append(outOption, TrafficLimitHeader(speed))
+       }
+
+       respHeader, _ := FindOption(options, responseHeader, nil)
+       if respHeader != nil {
+               outOption = append(outOption, GetResponseHeader(respHeader.(*http.Header)))
+       }
+
+       return outOption
+}
+
+// ChoiceCompletePartOption choices valid option supported by CompleteMulitiPart
+func ChoiceCompletePartOption(options []Option) []Option {
+       var outOption []Option
+
+       listener, _ := FindOption(options, progressListener, nil)
+       if listener != nil {
+               outOption = append(outOption, Progress(listener.(ProgressListener)))
+       }
+
+       payer, _ := FindOption(options, HTTPHeaderOssRequester, nil)
+       if payer != nil {
+               outOption = append(outOption, RequestPayer(PayerType(payer.(string))))
+       }
+
+       acl, _ := FindOption(options, HTTPHeaderOssObjectACL, nil)
+       if acl != nil {
+               outOption = append(outOption, ObjectACL(ACLType(acl.(string))))
+       }
+
+       callback, _ := FindOption(options, HTTPHeaderOssCallback, nil)
+       if callback != nil {
+               outOption = append(outOption, Callback(callback.(string)))
+       }
+
+       callbackVar, _ := FindOption(options, HTTPHeaderOssCallbackVar, nil)
+       if callbackVar != nil {
+               outOption = append(outOption, CallbackVar(callbackVar.(string)))
+       }
+
+       respHeader, _ := FindOption(options, responseHeader, nil)
+       if respHeader != nil {
+               outOption = append(outOption, GetResponseHeader(respHeader.(*http.Header)))
+       }
+
+       forbidOverWrite, _ := FindOption(options, HTTPHeaderOssForbidOverWrite, nil)
+       if forbidOverWrite != nil {
+               if forbidOverWrite.(string) == "true" {
+                       outOption = append(outOption, ForbidOverWrite(true))
+               } else {
+                       outOption = append(outOption, ForbidOverWrite(false))
+               }
+       }
+
+       return outOption
+}
+
+// ChoiceAbortPartOption choices valid option supported by AbortMultipartUpload
+func ChoiceAbortPartOption(options []Option) []Option {
+       var outOption []Option
+       payer, _ := FindOption(options, HTTPHeaderOssRequester, nil)
+       if payer != nil {
+               outOption = append(outOption, RequestPayer(PayerType(payer.(string))))
+       }
+
+       respHeader, _ := FindOption(options, responseHeader, nil)
+       if respHeader != nil {
+               outOption = append(outOption, GetResponseHeader(respHeader.(*http.Header)))
+       }
+
+       return outOption
+}
+
+// ChoiceHeadObjectOption choices valid option supported by HeadObject
+func ChoiceHeadObjectOption(options []Option) []Option {
+       var outOption []Option
+
+       // not select HTTPHeaderRange to get whole object length
+       payer, _ := FindOption(options, HTTPHeaderOssRequester, nil)
+       if payer != nil {
+               outOption = append(outOption, RequestPayer(PayerType(payer.(string))))
+       }
+
+       versionId, _ := FindOption(options, "versionId", nil)
+       if versionId != nil {
+               outOption = append(outOption, VersionId(versionId.(string)))
+       }
+
+       respHeader, _ := FindOption(options, responseHeader, nil)
+       if respHeader != nil {
+               outOption = append(outOption, GetResponseHeader(respHeader.(*http.Header)))
+       }
+
+       return outOption
+}
+
+func CheckBucketName(bucketName string) error {
+       nameLen := len(bucketName)
+       if nameLen < 3 || nameLen > 63 {
+               return fmt.Errorf("bucket name %s len is between [3-63],now is %d", bucketName, nameLen)
+       }
+
+       for _, v := range bucketName {
+               if !(('a' <= v && v <= 'z') || ('0' <= v && v <= '9') || v == '-') {
+                       return fmt.Errorf("bucket name %s can only include lowercase letters, numbers, and -", bucketName)
+               }
+       }
+       if bucketName[0] == '-' || bucketName[nameLen-1] == '-' {
+               return fmt.Errorf("bucket name %s must start and end with a lowercase letter or number", bucketName)
+       }
+       return nil
+}
+
+func GetReaderLen(reader io.Reader) (int64, error) {
+       var contentLength int64
+       var err error
+       switch v := reader.(type) {
+       case *bytes.Buffer:
+               contentLength = int64(v.Len())
+       case *bytes.Reader:
+               contentLength = int64(v.Len())
+       case *strings.Reader:
+               contentLength = int64(v.Len())
+       case *os.File:
+               fInfo, fError := v.Stat()
+               if fError != nil {
+                       err = fmt.Errorf("can't get reader content length,%s", fError.Error())
+               } else {
+                       contentLength = fInfo.Size()
+               }
+       case *io.LimitedReader:
+               contentLength = int64(v.N)
+       case *LimitedReadCloser:
+               contentLength = int64(v.N)
+       default:
+               err = fmt.Errorf("can't get reader content length,unkown reader type")
+       }
+       return contentLength, err
+}
+
+func LimitReadCloser(r io.Reader, n int64) io.Reader {
+       var lc LimitedReadCloser
+       lc.R = r
+       lc.N = n
+       return &lc
+}
+
+// LimitedRC support Close()
+type LimitedReadCloser struct {
+       io.LimitedReader
+}
+
+func (lc *LimitedReadCloser) Close() error {
+       if closer, ok := lc.R.(io.ReadCloser); ok {
+               return closer.Close()
+       }
+       return nil
+}
+
+type DiscardReadCloser struct {
+       RC      io.ReadCloser
+       Discard int
+}
+
+func (drc *DiscardReadCloser) Read(b []byte) (int, error) {
+       n, err := drc.RC.Read(b)
+       if drc.Discard == 0 || n <= 0 {
+               return n, err
+       }
+
+       if n <= drc.Discard {
+               drc.Discard -= n
+               return 0, err
+       }
+
+       realLen := n - drc.Discard
+       copy(b[0:realLen], b[drc.Discard:n])
+       drc.Discard = 0
+       return realLen, err
+}
+
+func (drc *DiscardReadCloser) Close() error {
+       closer, ok := drc.RC.(io.ReadCloser)
+       if ok {
+               return closer.Close()
+       }
+       return nil
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils_test.go
new file mode 100644 (file)
index 0000000..dde929a
--- /dev/null
@@ -0,0 +1,259 @@
+package oss
+
+import (
+       "strings"
+
+       . "gopkg.in/check.v1"
+)
+
+type OssUtilsSuite struct{}
+
+var _ = Suite(&OssUtilsSuite{})
+
+func (s *OssUtilsSuite) TestUtilsTime(c *C) {
+       c.Assert(GetNowSec() > 1448597674, Equals, true)
+       c.Assert(GetNowNanoSec() > 1448597674000000000, Equals, true)
+       c.Assert(len(GetNowGMT()), Equals, len("Fri, 27 Nov 2015 04:14:34 GMT"))
+}
+
+func (s *OssUtilsSuite) TestUtilsSplitFile(c *C) {
+       localFile := "../sample/BingWallpaper-2015-11-07.jpg"
+
+       // Num
+       parts, err := SplitFileByPartNum(localFile, 4)
+       c.Assert(err, IsNil)
+       c.Assert(len(parts), Equals, 4)
+       testLogger.Println("parts 4:", parts)
+       for i, part := range parts {
+               c.Assert(part.Number, Equals, i+1)
+               c.Assert(part.Offset, Equals, int64(i*120512))
+               c.Assert(part.Size, Equals, int64(120512))
+       }
+
+       parts, err = SplitFileByPartNum(localFile, 5)
+       c.Assert(err, IsNil)
+       c.Assert(len(parts), Equals, 5)
+       testLogger.Println("parts 5:", parts)
+       for i, part := range parts {
+               c.Assert(part.Number, Equals, i+1)
+               c.Assert(part.Offset, Equals, int64(i*96409))
+       }
+
+       _, err = SplitFileByPartNum(localFile, 10001)
+       c.Assert(err, NotNil)
+
+       _, err = SplitFileByPartNum(localFile, 0)
+       c.Assert(err, NotNil)
+
+       _, err = SplitFileByPartNum(localFile, -1)
+       c.Assert(err, NotNil)
+
+       _, err = SplitFileByPartNum("notexist", 1024)
+       c.Assert(err, NotNil)
+
+       // Size
+       parts, err = SplitFileByPartSize(localFile, 120512)
+       c.Assert(err, IsNil)
+       c.Assert(len(parts), Equals, 4)
+       testLogger.Println("parts 4:", parts)
+       for i, part := range parts {
+               c.Assert(part.Number, Equals, i+1)
+               c.Assert(part.Offset, Equals, int64(i*120512))
+               c.Assert(part.Size, Equals, int64(120512))
+       }
+
+       parts, err = SplitFileByPartSize(localFile, 96409)
+       c.Assert(err, IsNil)
+       c.Assert(len(parts), Equals, 6)
+       testLogger.Println("parts 6:", parts)
+       for i, part := range parts {
+               c.Assert(part.Number, Equals, i+1)
+               c.Assert(part.Offset, Equals, int64(i*96409))
+       }
+
+       _, err = SplitFileByPartSize(localFile, 0)
+       c.Assert(err, NotNil)
+
+       _, err = SplitFileByPartSize(localFile, -1)
+       c.Assert(err, NotNil)
+
+       _, err = SplitFileByPartSize(localFile, 10)
+       c.Assert(err, NotNil)
+
+       _, err = SplitFileByPartSize("noexist", 120512)
+       c.Assert(err, NotNil)
+}
+
+func (s *OssUtilsSuite) TestUtilsFileExt(c *C) {
+       c.Assert(strings.Contains(TypeByExtension("test.txt"), "text/plain"), Equals, true)
+       c.Assert(TypeByExtension("test.jpg"), Equals, "image/jpeg")
+       c.Assert(TypeByExtension("test.pdf"), Equals, "application/pdf")
+       c.Assert(TypeByExtension("test"), Equals, "")
+       c.Assert(strings.Contains(TypeByExtension("/root/dir/test.txt"), "text/plain"), Equals, true)
+       c.Assert(strings.Contains(TypeByExtension("root/dir/test.txt"), "text/plain"), Equals, true)
+       c.Assert(strings.Contains(TypeByExtension("root\\dir\\test.txt"), "text/plain"), Equals, true)
+       c.Assert(strings.Contains(TypeByExtension("D:\\work\\dir\\test.txt"), "text/plain"), Equals, true)
+}
+
+func (s *OssUtilsSuite) TestGetPartEnd(c *C) {
+       end := GetPartEnd(3, 10, 3)
+       c.Assert(end, Equals, int64(5))
+
+       end = GetPartEnd(9, 10, 3)
+       c.Assert(end, Equals, int64(9))
+
+       end = GetPartEnd(7, 10, 3)
+       c.Assert(end, Equals, int64(9))
+}
+
+func (s *OssUtilsSuite) TestParseRange(c *C) {
+       // InvalidRange bytes==M-N
+       _, err := ParseRange("bytes==M-N")
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "InvalidRange bytes==M-N")
+
+       // InvalidRange ranges=M-N
+       _, err = ParseRange("ranges=M-N")
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "InvalidRange ranges=M-N")
+
+       // InvalidRange ranges=M-N
+       _, err = ParseRange("bytes=M-N")
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "InvalidRange bytes=M-N")
+
+       // InvalidRange ranges=M-
+       _, err = ParseRange("bytes=M-")
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "InvalidRange bytes=M-")
+
+       // InvalidRange ranges=-N
+       _, err = ParseRange("bytes=-N")
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "InvalidRange bytes=-N")
+
+       // InvalidRange ranges=-0
+       _, err = ParseRange("bytes=-0")
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "InvalidRange bytes=-0")
+
+       // InvalidRange bytes=1-2-3
+       _, err = ParseRange("bytes=1-2-3")
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "InvalidRange bytes=1-2-3")
+
+       // InvalidRange bytes=1-N
+       _, err = ParseRange("bytes=1-N")
+       c.Assert(err, NotNil)
+       c.Assert(err.Error(), Equals, "InvalidRange bytes=1-N")
+
+       // Ranges=M-N
+       ur, err := ParseRange("bytes=1024-4096")
+       c.Assert(err, IsNil)
+       c.Assert(ur.Start, Equals, (int64)(1024))
+       c.Assert(ur.End, Equals, (int64)(4096))
+       c.Assert(ur.HasStart, Equals, true)
+       c.Assert(ur.HasEnd, Equals, true)
+
+       // Ranges=M-N,X-Y
+       ur, err = ParseRange("bytes=1024-4096,2048-4096")
+       c.Assert(err, IsNil)
+       c.Assert(ur.Start, Equals, (int64)(1024))
+       c.Assert(ur.End, Equals, (int64)(4096))
+       c.Assert(ur.HasStart, Equals, true)
+       c.Assert(ur.HasEnd, Equals, true)
+
+       // Ranges=M-
+       ur, err = ParseRange("bytes=1024-")
+       c.Assert(err, IsNil)
+       c.Assert(ur.Start, Equals, (int64)(1024))
+       c.Assert(ur.End, Equals, (int64)(0))
+       c.Assert(ur.HasStart, Equals, true)
+       c.Assert(ur.HasEnd, Equals, false)
+
+       // Ranges=-N
+       ur, err = ParseRange("bytes=-4096")
+       c.Assert(err, IsNil)
+       c.Assert(ur.Start, Equals, (int64)(0))
+       c.Assert(ur.End, Equals, (int64)(4096))
+       c.Assert(ur.HasStart, Equals, false)
+       c.Assert(ur.HasEnd, Equals, true)
+}
+
+func (s *OssUtilsSuite) TestAdjustRange(c *C) {
+       // Nil
+       start, end := AdjustRange(nil, 8192)
+       c.Assert(start, Equals, (int64)(0))
+       c.Assert(end, Equals, (int64)(8192))
+
+       // 1024-4096
+       ur := &UnpackedRange{true, true, 1024, 4095}
+       start, end = AdjustRange(ur, 8192)
+       c.Assert(start, Equals, (int64)(1024))
+       c.Assert(end, Equals, (int64)(4096))
+
+       // 1024-
+       ur = &UnpackedRange{true, false, 1024, 4096}
+       start, end = AdjustRange(ur, 8192)
+       c.Assert(start, Equals, (int64)(1024))
+       c.Assert(end, Equals, (int64)(8192))
+
+       // -4096
+       ur = &UnpackedRange{false, true, 1024, 4096}
+       start, end = AdjustRange(ur, 8192)
+       c.Assert(start, Equals, (int64)(4096))
+       c.Assert(end, Equals, (int64)(8192))
+
+       // Invalid range 4096-1024
+       ur = &UnpackedRange{true, true, 4096, 1024}
+       start, end = AdjustRange(ur, 8192)
+       c.Assert(start, Equals, (int64)(0))
+       c.Assert(end, Equals, (int64)(8192))
+
+       // Invalid range -1-
+       ur = &UnpackedRange{true, false, -1, 0}
+       start, end = AdjustRange(ur, 8192)
+       c.Assert(start, Equals, (int64)(0))
+       c.Assert(end, Equals, (int64)(8192))
+
+       // Invalid range -9999
+       ur = &UnpackedRange{false, true, 0, 9999}
+       start, end = AdjustRange(ur, 8192)
+       c.Assert(start, Equals, (int64)(0))
+       c.Assert(end, Equals, (int64)(8192))
+}
+
+func (s *OssUtilsSuite) TestUtilCheckBucketName(c *C) {
+       err := CheckBucketName("a")
+       c.Assert(err, NotNil)
+
+       err = CheckBucketName("a11111111111111111111111111111nbbbbbbbbbbbbbbbbbbbbbbbbbbbqqqqqqqqqqqqqqqqqqqq")
+       c.Assert(err, NotNil)
+
+       err = CheckBucketName("-abcd")
+       c.Assert(err, NotNil)
+
+       err = CheckBucketName("abcd-")
+       c.Assert(err, NotNil)
+
+       err = CheckBucketName("abcD")
+       c.Assert(err, NotNil)
+
+       err = CheckBucketName("abc 1")
+       c.Assert(err, NotNil)
+
+       err = CheckBucketName("abc&1")
+       c.Assert(err, NotNil)
+
+       err = CheckBucketName("abc-1")
+       c.Assert(err, IsNil)
+
+       err = CheckBucketName("1bc-1")
+       c.Assert(err, IsNil)
+
+       err = CheckBucketName("111-1")
+       c.Assert(err, IsNil)
+
+       err = CheckBucketName("abc123-def1")
+       c.Assert(err, IsNil)
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample.go
new file mode 100644 (file)
index 0000000..072fe85
--- /dev/null
@@ -0,0 +1,59 @@
+// main of samples
+
+package main
+
+import (
+       "flag"
+       "fmt"
+       "os"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/sample"
+)
+
+// sampleMap contains all samples
+var sampleMap = map[string]interface{}{
+       "CreateBucketSample":          sample.CreateBucketSample,
+       "NewBucketSample":             sample.NewBucketSample,
+       "ListBucketsSample":           sample.ListBucketsSample,
+       "BucketACLSample":             sample.BucketACLSample,
+       "BucketLifecycleSample":       sample.BucketLifecycleSample,
+       "BucketRefererSample":         sample.BucketRefererSample,
+       "BucketLoggingSample":         sample.BucketLoggingSample,
+       "BucketWebsiteSample":         sample.BucketWebsiteSample,
+       "BucketCORSSample":            sample.BucketCORSSample,
+       "BucketPolicySample":          sample.BucketPolicySample,
+       "BucketrRequestPaymentSample": sample.BucketrRequestPaymentSample,
+       "BucketQoSInfoSample":         sample.BucketQoSInfoSample,
+       "BucketInventorySample":       sample.BucketInventorySample,
+       "ObjectACLSample":             sample.ObjectACLSample,
+       "ObjectMetaSample":            sample.ObjectMetaSample,
+       "ListObjectsSample":           sample.ListObjectsSample,
+       "DeleteObjectSample":          sample.DeleteObjectSample,
+       "AppendObjectSample":          sample.AppendObjectSample,
+       "CopyObjectSample":            sample.CopyObjectSample,
+       "PutObjectSample":             sample.PutObjectSample,
+       "GetObjectSample":             sample.GetObjectSample,
+       "CnameSample":                 sample.CnameSample,
+       "SignURLSample":               sample.SignURLSample,
+       "ArchiveSample":               sample.ArchiveSample,
+       "ObjectTaggingSample":         sample.ObjectTaggingSample,
+       "BucketEncryptionSample":      sample.BucketEncryptionSample,
+       "SelectObjectSample":          sample.SelectObjectSample,
+}
+
+func main() {
+       var name string
+       flag.StringVar(&name, "name", "", "Waiting for a sample of execution")
+       flag.Parse()
+
+       if len(name) <= 0 {
+               fmt.Println("please enter your sample's name. like '-name CreateBucketSample'")
+               os.Exit(-1)
+       } else {
+               if sampleMap[name] == nil {
+                       fmt.Println("the " + name + "is not exist.")
+                       os.Exit(-1)
+               }
+               sampleMap[name].(func())()
+       }
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/BingWallpaper-2015-11-07.jpg b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/BingWallpaper-2015-11-07.jpg
new file mode 100644 (file)
index 0000000..90960f0
Binary files /dev/null and b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/BingWallpaper-2015-11-07.jpg differ
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/The Go Programming Language.html b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/The Go Programming Language.html
new file mode 100644 (file)
index 0000000..ee91f07
--- /dev/null
@@ -0,0 +1,3663 @@
+<!DOCTYPE html>
+<!-- saved from url=(0032)https://golang.org/pkg/net/http/ -->
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="theme-color" content="#375EAB">
+
+  <title>http - The Go Programming Language</title>
+
+<link type="text/css" rel="stylesheet" href="./http - The Go Programming Language_files/style.css">
+
+<link rel="search" type="application/opensearchdescription+xml" title="godoc" href="https://golang.org/opensearch.xml">
+
+<link rel="stylesheet" href="./http - The Go Programming Language_files/jquery.treeview.css">
+<script type="text/javascript" async="" src="./http - The Go Programming Language_files/ga.js"></script><script type="text/javascript">window.initFuncs = [];</script>
+<script type="text/javascript">
+var _gaq = _gaq || [];
+_gaq.push(["_setAccount", "UA-11222381-2"]);
+_gaq.push(["b._setAccount", "UA-49880327-6"]);
+window.trackPageview = function() {
+  _gaq.push(["_trackPageview", location.pathname+location.hash]);
+  _gaq.push(["b._trackPageview", location.pathname+location.hash]);
+};
+window.trackPageview();
+window.trackEvent = function(category, action, opt_label, opt_value, opt_noninteraction) {
+  _gaq.push(["_trackEvent", category, action, opt_label, opt_value, opt_noninteraction]);
+  _gaq.push(["b._trackEvent", category, action, opt_label, opt_value, opt_noninteraction]);
+};
+</script>
+</head>
+<body>
+
+<div id="lowframe" style="position: fixed; bottom: 0; left: 0; height: 0; width: 100%; border-top: thin solid grey; background-color: white; overflow: auto;">
+...
+</div><!-- #lowframe -->
+
+<div id="topbar" class="wide"><div class="container">
+<div class="top-heading" id="heading-wide"><a href="https://golang.org/">The Go Programming Language</a></div>
+<div class="top-heading" id="heading-narrow"><a href="https://golang.org/">Go</a></div>
+<a href="https://golang.org/pkg/net/http/#" id="menu-button"><span id="menu-button-arrow">▽</span></a>
+<form method="GET" action="https://golang.org/search">
+<div id="menu" style="min-width: 60px;">
+<a href="https://golang.org/doc/">Documents</a>
+<a href="https://golang.org/pkg/">Packages</a>
+<a href="https://golang.org/project/">The Project</a>
+<a href="https://golang.org/help/">Help</a>
+<a href="https://golang.org/blog/">Blog</a>
+
+<a id="playgroundButton" href="http://play.golang.org/" title="Show Go Playground" style="display: inline;">Play</a>
+
+<input type="text" id="search" name="q" class="inactive" value="Search" placeholder="Search">
+</div>
+</form>
+
+</div></div>
+
+
+<div id="playground" class="play">
+       <div class="input"><textarea class="code">package main
+
+import "fmt"
+
+func main() {
+       fmt.Println("Hello, 世界")
+}</textarea></div>
+       <div class="output"></div>
+       <div class="buttons">
+               <a class="run" title="Run this code [shift-enter]">Run</a>
+               <a class="fmt" title="Format this code">Format</a>
+               
+       </div>
+</div>
+
+
+<div id="page" class="wide" tabindex="-1" style="outline: 0px;">
+<div class="container">
+
+
+  <h1>Package http</h1>
+
+
+
+
+<div id="nav"></div>
+
+
+<!--
+       Copyright 2009 The Go Authors. All rights reserved.
+       Use of this source code is governed by a BSD-style
+       license that can be found in the LICENSE file.
+-->
+<!--
+       Note: Static (i.e., not template-generated) href and id
+       attributes start with "pkg-" to make it impossible for
+       them to conflict with generated attributes (some of which
+       correspond to Go identifiers).
+-->
+
+       <script type="text/javascript">
+       document.ANALYSIS_DATA = null;
+       document.CALLGRAPH = null;
+       </script>
+
+       
+               
+               <div id="short-nav">
+                       <dl>
+                       <dd><code>import "net/http"</code></dd>
+                       </dl>
+                       <dl>
+                       <dd><a href="https://golang.org/pkg/net/http/#pkg-overview" class="overviewLink">Overview</a></dd>
+                       <dd><a href="https://golang.org/pkg/net/http/#pkg-index" class="indexLink">Index</a></dd>
+                       
+                               <dd><a href="https://golang.org/pkg/net/http/#pkg-examples" class="examplesLink">Examples</a></dd>
+                       
+                       
+                               <dd><a href="https://golang.org/pkg/net/http/#pkg-subdirectories">Subdirectories</a></dd>
+                       
+                       </dl>
+               </div>
+               <!-- The package's Name is printed as title by the top-level template -->
+               <div id="pkg-overview" class="toggleVisible">
+                       <div class="collapsed">
+                               <h2 class="toggleButton" title="Click to show Overview section">Overview ▹</h2>
+                       </div>
+                       <div class="expanded">
+                               <h2 class="toggleButton" title="Click to hide Overview section">Overview ▾</h2>
+                               <p>
+Package http provides HTTP client and server implementations.
+</p>
+<p>
+Get, Head, Post, and PostForm make HTTP (or HTTPS) requests:
+</p>
+<pre>resp, err := http.Get("<a href="http://example.com/">http://example.com/</a>")
+...
+resp, err := http.Post("<a href="http://example.com/upload">http://example.com/upload</a>", "image/jpeg", &amp;buf)
+...
+resp, err := http.PostForm("<a href="http://example.com/form">http://example.com/form</a>",
+       url.Values{"key": {"Value"}, "id": {"123"}})
+</pre>
+<p>
+The client must close the response body when finished with it:
+</p>
+<pre>resp, err := http.Get("<a href="http://example.com/">http://example.com/</a>")
+if err != nil {
+       // handle error
+}
+defer resp.Body.Close()
+body, err := ioutil.ReadAll(resp.Body)
+// ...
+</pre>
+<p>
+For control over HTTP client headers, redirect policy, and other
+settings, create a Client:
+</p>
+<pre>client := &amp;http.Client{
+       CheckRedirect: redirectPolicyFunc,
+}
+
+resp, err := client.Get("<a href="http://example.com/">http://example.com</a>")
+// ...
+
+req, err := http.NewRequest("GET", "<a href="http://example.com/">http://example.com</a>", nil)
+// ...
+req.Header.Add("If-None-Match", `W/"wyzzy"`)
+resp, err := client.Do(req)
+// ...
+</pre>
+<p>
+For control over proxies, TLS configuration, keep-alives,
+compression, and other settings, create a Transport:
+</p>
+<pre>tr := &amp;http.Transport{
+       TLSClientConfig:    &amp;tls.Config{RootCAs: pool},
+       DisableCompression: true,
+}
+client := &amp;http.Client{Transport: tr}
+resp, err := client.Get("<a href="https://example.com/">https://example.com</a>")
+</pre>
+<p>
+Clients and Transports are safe for concurrent use by multiple
+goroutines and for efficiency should only be created once and re-used.
+</p>
+<p>
+ListenAndServe starts an HTTP server with a given address and handler.
+The handler is usually nil, which means to use DefaultServeMux.
+Handle and HandleFunc add handlers to DefaultServeMux:
+</p>
+<pre>http.Handle("/foo", fooHandler)
+
+http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
+       fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
+})
+
+log.Fatal(http.ListenAndServe(":8080", nil))
+</pre>
+<p>
+More control over the server's behavior is available by creating a
+custom Server:
+</p>
+<pre>s := &amp;http.Server{
+       Addr:           ":8080",
+       Handler:        myHandler,
+       ReadTimeout:    10 * time.Second,
+       WriteTimeout:   10 * time.Second,
+       MaxHeaderBytes: 1 &lt;&lt; 20,
+}
+log.Fatal(s.ListenAndServe())
+</pre>
+
+                       </div>
+               </div>
+               
+
+               <div id="pkg-index" class="toggleVisible">
+               <div class="collapsed">
+                       <h2 class="toggleButton" title="Click to show Index section">Index ▹</h2>
+               </div>
+               <div class="expanded">
+                       <h2 class="toggleButton" title="Click to hide Index section">Index ▾</h2>
+
+               <!-- Table of contents for API; must be named manual-nav to turn off auto nav. -->
+                       <div id="manual-nav">
+                       <dl>
+                       
+                               <dd><a href="https://golang.org/pkg/net/http/#pkg-constants">Constants</a></dd>
+                       
+                       
+                               <dd><a href="https://golang.org/pkg/net/http/#pkg-variables">Variables</a></dd>
+                       
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#CanonicalHeaderKey">func CanonicalHeaderKey(s string) string</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#DetectContentType">func DetectContentType(data []byte) string</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Error">func Error(w ResponseWriter, error string, code int)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Handle">func Handle(pattern string, handler Handler)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#HandleFunc">func HandleFunc(pattern string, handler func(ResponseWriter, *Request))</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ListenAndServe">func ListenAndServe(addr string, handler Handler) error</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ListenAndServeTLS">func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#MaxBytesReader">func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#NotFound">func NotFound(w ResponseWriter, r *Request)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ParseHTTPVersion">func ParseHTTPVersion(vers string) (major, minor int, ok bool)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ParseTime">func ParseTime(text string) (t time.Time, err error)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ProxyFromEnvironment">func ProxyFromEnvironment(req *Request) (*url.URL, error)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ProxyURL">func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Redirect">func Redirect(w ResponseWriter, r *Request, urlStr string, code int)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Serve">func Serve(l net.Listener, handler Handler) error</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ServeContent">func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ServeFile">func ServeFile(w ResponseWriter, r *Request, name string)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#SetCookie">func SetCookie(w ResponseWriter, cookie *Cookie)</a></dd>
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#StatusText">func StatusText(code int) string</a></dd>
+                       
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Client">type Client</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Client.Do">func (c *Client) Do(req *Request) (resp *Response, err error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Client.Get">func (c *Client) Get(url string) (resp *Response, err error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Client.Head">func (c *Client) Head(url string) (resp *Response, err error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Client.Post">func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Client.PostForm">func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error)</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#CloseNotifier">type CloseNotifier</a></dd>
+                               
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ConnState">type ConnState</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ConnState.String">func (c ConnState) String() string</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Cookie">type Cookie</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Cookie.String">func (c *Cookie) String() string</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#CookieJar">type CookieJar</a></dd>
+                               
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Dir">type Dir</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Dir.Open">func (d Dir) Open(name string) (File, error)</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#File">type File</a></dd>
+                               
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#FileSystem">type FileSystem</a></dd>
+                               
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Flusher">type Flusher</a></dd>
+                               
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Handler">type Handler</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#FileServer">func FileServer(root FileSystem) Handler</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#NotFoundHandler">func NotFoundHandler() Handler</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#RedirectHandler">func RedirectHandler(url string, code int) Handler</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#StripPrefix">func StripPrefix(prefix string, h Handler) Handler</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#TimeoutHandler">func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler</a></dd>
+                               
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#HandlerFunc">type HandlerFunc</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#HandlerFunc.ServeHTTP">func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Header">type Header</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.Add">func (h Header) Add(key, value string)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.Del">func (h Header) Del(key string)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.Get">func (h Header) Get(key string) string</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.Set">func (h Header) Set(key, value string)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.Write">func (h Header) Write(w io.Writer) error</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.WriteSubset">func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Hijacker">type Hijacker</a></dd>
+                               
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ProtocolError">type ProtocolError</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ProtocolError.Error">func (err *ProtocolError) Error() string</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Request">type Request</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#NewRequest">func NewRequest(method, urlStr string, body io.Reader) (*Request, error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ReadRequest">func ReadRequest(b *bufio.Reader) (req *Request, err error)</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.AddCookie">func (r *Request) AddCookie(c *Cookie)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.BasicAuth">func (r *Request) BasicAuth() (username, password string, ok bool)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.Cookie">func (r *Request) Cookie(name string) (*Cookie, error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.Cookies">func (r *Request) Cookies() []*Cookie</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.FormFile">func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.FormValue">func (r *Request) FormValue(key string) string</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.MultipartReader">func (r *Request) MultipartReader() (*multipart.Reader, error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.ParseForm">func (r *Request) ParseForm() error</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.ParseMultipartForm">func (r *Request) ParseMultipartForm(maxMemory int64) error</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.PostFormValue">func (r *Request) PostFormValue(key string) string</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.ProtoAtLeast">func (r *Request) ProtoAtLeast(major, minor int) bool</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.Referer">func (r *Request) Referer() string</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.SetBasicAuth">func (r *Request) SetBasicAuth(username, password string)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.UserAgent">func (r *Request) UserAgent() string</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.Write">func (r *Request) Write(w io.Writer) error</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.WriteProxy">func (r *Request) WriteProxy(w io.Writer) error</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Response">type Response</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Get">func Get(url string) (resp *Response, err error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Head">func Head(url string) (resp *Response, err error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Post">func Post(url string, bodyType string, body io.Reader) (resp *Response, err error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#PostForm">func PostForm(url string, data url.Values) (resp *Response, err error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ReadResponse">func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Response.Cookies">func (r *Response) Cookies() []*Cookie</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Response.Location">func (r *Response) Location() (*url.URL, error)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Response.ProtoAtLeast">func (r *Response) ProtoAtLeast(major, minor int) bool</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Response.Write">func (r *Response) Write(w io.Writer) error</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ResponseWriter">type ResponseWriter</a></dd>
+                               
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#RoundTripper">type RoundTripper</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#NewFileTransport">func NewFileTransport(fs FileSystem) RoundTripper</a></dd>
+                               
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#ServeMux">type ServeMux</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#NewServeMux">func NewServeMux() *ServeMux</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ServeMux.Handle">func (mux *ServeMux) Handle(pattern string, handler Handler)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ServeMux.HandleFunc">func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ServeMux.Handler">func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ServeMux.ServeHTTP">func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Server">type Server</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Server.ListenAndServe">func (srv *Server) ListenAndServe() error</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Server.ListenAndServeTLS">func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Server.Serve">func (srv *Server) Serve(l net.Listener) error</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Server.SetKeepAlivesEnabled">func (srv *Server) SetKeepAlivesEnabled(v bool)</a></dd>
+                               
+                       
+                               
+                               <dd><a href="https://golang.org/pkg/net/http/#Transport">type Transport</a></dd>
+                               
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Transport.CancelRequest">func (t *Transport) CancelRequest(req *Request)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Transport.CloseIdleConnections">func (t *Transport) CloseIdleConnections()</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Transport.RegisterProtocol">func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper)</a></dd>
+                               
+                                       
+                                       <dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Transport.RoundTrip">func (t *Transport) RoundTrip(req *Request) (resp *Response, err error)</a></dd>
+                               
+                       
+                       
+                       </dl>
+                       </div><!-- #manual-nav -->
+
+               
+               <div id="pkg-examples">
+                       <h4>Examples</h4>
+                       <dl>
+                       
+                       <dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_FileServer">FileServer</a></dd>
+                       
+                       <dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_FileServer_stripPrefix">FileServer (StripPrefix)</a></dd>
+                       
+                       <dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_Get">Get</a></dd>
+                       
+                       <dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_Hijacker">Hijacker</a></dd>
+                       
+                       <dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_ResponseWriter_trailers">ResponseWriter (Trailers)</a></dd>
+                       
+                       <dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_ServeMux_Handle">ServeMux.Handle</a></dd>
+                       
+                       <dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_StripPrefix">StripPrefix</a></dd>
+                       
+                       </dl>
+               </div>
+               
+
+               
+                       <h4>Package files</h4>
+                       <p>
+                       <span style="font-size:90%">
+                       
+                               <a href="https://golang.org/src/net/http/client.go">client.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/cookie.go">cookie.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/doc.go">doc.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/filetransport.go">filetransport.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/fs.go">fs.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/header.go">header.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/jar.go">jar.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/lex.go">lex.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/request.go">request.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/response.go">response.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/server.go">server.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/sniff.go">sniff.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/status.go">status.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/transfer.go">transfer.go</a>
+                       
+                               <a href="https://golang.org/src/net/http/transport.go">transport.go</a>
+                       
+                       </span>
+                       </p>
+               
+               </div><!-- .expanded -->
+               </div><!-- #pkg-index -->
+
+               <div id="pkg-callgraph" class="toggle" style="display: none">
+               <div class="collapsed">
+                       <h2 class="toggleButton" title="Click to show Internal Call Graph section">Internal call graph ▹</h2>
+               </div> <!-- .expanded -->
+               <div class="expanded">
+                       <h2 class="toggleButton" title="Click to hide Internal Call Graph section">Internal call graph ▾</h2>
+                       <p>
+                         In the call graph viewer below, each node
+                         is a function belonging to this package
+                         and its children are the functions it
+                         calls—perhaps dynamically.
+                       </p>
+                       <p>
+                         The root nodes are the entry points of the
+                         package: functions that may be called from
+                         outside the package.
+                         There may be non-exported or anonymous
+                         functions among them if they are called
+                         dynamically from another package.
+                       </p>
+                       <p>
+                         Click a node to visit that function's source code.
+                         From there you can visit its callers by
+                         clicking its declaring <code>func</code>
+                         token.
+                       </p>
+                       <p>
+                         Functions may be omitted if they were
+                         determined to be unreachable in the
+                         particular programs or tests that were
+                         analyzed.
+                       </p>
+                       <!-- Zero means show all package entry points. -->
+                       <ul style="margin-left: 0.5in" id="callgraph-0" class="treeview"></ul>
+               </div>
+               </div> <!-- #pkg-callgraph -->
+
+               
+                       <h2 id="pkg-constants">Constants</h2>
+                       
+                               <pre>const (
+        <span id="StatusContinue">StatusContinue</span>           = 100
+        <span id="StatusSwitchingProtocols">StatusSwitchingProtocols</span> = 101
+
+        <span id="StatusOK">StatusOK</span>                   = 200
+        <span id="StatusCreated">StatusCreated</span>              = 201
+        <span id="StatusAccepted">StatusAccepted</span>             = 202
+        <span id="StatusNonAuthoritativeInfo">StatusNonAuthoritativeInfo</span> = 203
+        <span id="StatusNoContent">StatusNoContent</span>            = 204
+        <span id="StatusResetContent">StatusResetContent</span>         = 205
+        <span id="StatusPartialContent">StatusPartialContent</span>       = 206
+
+        <span id="StatusMultipleChoices">StatusMultipleChoices</span>   = 300
+        <span id="StatusMovedPermanently">StatusMovedPermanently</span>  = 301
+        <span id="StatusFound">StatusFound</span>             = 302
+        <span id="StatusSeeOther">StatusSeeOther</span>          = 303
+        <span id="StatusNotModified">StatusNotModified</span>       = 304
+        <span id="StatusUseProxy">StatusUseProxy</span>          = 305
+        <span id="StatusTemporaryRedirect">StatusTemporaryRedirect</span> = 307
+
+        <span id="StatusBadRequest">StatusBadRequest</span>                   = 400
+        <span id="StatusUnauthorized">StatusUnauthorized</span>                 = 401
+        <span id="StatusPaymentRequired">StatusPaymentRequired</span>              = 402
+        <span id="StatusForbidden">StatusForbidden</span>                    = 403
+        <span id="StatusNotFound">StatusNotFound</span>                     = 404
+        <span id="StatusMethodNotAllowed">StatusMethodNotAllowed</span>             = 405
+        <span id="StatusNotAcceptable">StatusNotAcceptable</span>                = 406
+        <span id="StatusProxyAuthRequired">StatusProxyAuthRequired</span>            = 407
+        <span id="StatusRequestTimeout">StatusRequestTimeout</span>               = 408
+        <span id="StatusConflict">StatusConflict</span>                     = 409
+        <span id="StatusGone">StatusGone</span>                         = 410
+        <span id="StatusLengthRequired">StatusLengthRequired</span>               = 411
+        <span id="StatusPreconditionFailed">StatusPreconditionFailed</span>           = 412
+        <span id="StatusRequestEntityTooLarge">StatusRequestEntityTooLarge</span>        = 413
+        <span id="StatusRequestURITooLong">StatusRequestURITooLong</span>            = 414
+        <span id="StatusUnsupportedMediaType">StatusUnsupportedMediaType</span>         = 415
+        <span id="StatusRequestedRangeNotSatisfiable">StatusRequestedRangeNotSatisfiable</span> = 416
+        <span id="StatusExpectationFailed">StatusExpectationFailed</span>            = 417
+        <span id="StatusTeapot">StatusTeapot</span>                       = 418
+
+        <span id="StatusInternalServerError">StatusInternalServerError</span>     = 500
+        <span id="StatusNotImplemented">StatusNotImplemented</span>          = 501
+        <span id="StatusBadGateway">StatusBadGateway</span>              = 502
+        <span id="StatusServiceUnavailable">StatusServiceUnavailable</span>      = 503
+        <span id="StatusGatewayTimeout">StatusGatewayTimeout</span>          = 504
+        <span id="StatusHTTPVersionNotSupported">StatusHTTPVersionNotSupported</span> = 505
+)</pre>
+                               <p>
+HTTP status codes, defined in RFC 2616.
+</p>
+
+                       
+                               <pre>const <span id="DefaultMaxHeaderBytes">DefaultMaxHeaderBytes</span> = 1 &lt;&lt; 20 <span class="comment">// 1 MB</span>
+</pre>
+                               <p>
+DefaultMaxHeaderBytes is the maximum permitted size of the headers
+in an HTTP request.
+This can be overridden by setting Server.MaxHeaderBytes.
+</p>
+
+                       
+                               <pre>const <span id="DefaultMaxIdleConnsPerHost">DefaultMaxIdleConnsPerHost</span> = 2</pre>
+                               <p>
+DefaultMaxIdleConnsPerHost is the default value of Transport's
+MaxIdleConnsPerHost.
+</p>
+
+                       
+                               <pre>const <span id="TimeFormat">TimeFormat</span> = "Mon, 02 Jan 2006 15:04:05 GMT"</pre>
+                               <p>
+TimeFormat is the time format to use with
+time.Parse and time.Time.Format when parsing
+or generating times in HTTP headers.
+It is like time.RFC1123 but hard codes GMT as the time zone.
+</p>
+
+                       
+               
+               
+                       <h2 id="pkg-variables">Variables</h2>
+                       
+                               <pre>var (
+        <span id="ErrHeaderTooLong">ErrHeaderTooLong</span>        = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"header too long"}
+        <span id="ErrShortBody">ErrShortBody</span>            = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"entity body too short"}
+        <span id="ErrNotSupported">ErrNotSupported</span>         = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"feature not supported"}
+        <span id="ErrUnexpectedTrailer">ErrUnexpectedTrailer</span>    = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"trailer header without chunked transfer encoding"}
+        <span id="ErrMissingContentLength">ErrMissingContentLength</span> = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"missing ContentLength in HEAD response"}
+        <span id="ErrNotMultipart">ErrNotMultipart</span>         = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"request Content-Type isn't multipart/form-data"}
+        <span id="ErrMissingBoundary">ErrMissingBoundary</span>      = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"no multipart boundary param in Content-Type"}
+)</pre>
+                               
+                       
+                               <pre>var (
+        <span id="ErrWriteAfterFlush">ErrWriteAfterFlush</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("Conn.Write called after Flush")
+        <span id="ErrBodyNotAllowed">ErrBodyNotAllowed</span>  = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: request method or response status code does not allow body")
+        <span id="ErrHijacked">ErrHijacked</span>        = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("Conn has been hijacked")
+        <span id="ErrContentLength">ErrContentLength</span>   = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("Conn.Write wrote more than the declared Content-Length")
+)</pre>
+                               <p>
+Errors introduced by the HTTP server.
+</p>
+
+                       
+                               <pre>var <span id="DefaultClient">DefaultClient</span> = &amp;<a href="https://golang.org/pkg/net/http/#Client">Client</a>{}</pre>
+                               <p>
+DefaultClient is the default Client and is used by Get, Head, and Post.
+</p>
+
+                       
+                               <pre>var <span id="DefaultServeMux">DefaultServeMux</span> = <a href="https://golang.org/pkg/net/http/#NewServeMux">NewServeMux</a>()</pre>
+                               <p>
+DefaultServeMux is the default ServeMux used by Serve.
+</p>
+
+                       
+                               <pre>var <span id="ErrBodyReadAfterClose">ErrBodyReadAfterClose</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: invalid Read on closed Body")</pre>
+                               <p>
+ErrBodyReadAfterClose is returned when reading a Request or Response
+Body after the body has been closed. This typically happens when the body is
+read after an HTTP Handler calls WriteHeader or Write on its
+ResponseWriter.
+</p>
+
+                       
+                               <pre>var <span id="ErrHandlerTimeout">ErrHandlerTimeout</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: Handler timeout")</pre>
+                               <p>
+ErrHandlerTimeout is returned on ResponseWriter Write calls
+in handlers which have timed out.
+</p>
+
+                       
+                               <pre>var <span id="ErrLineTooLong">ErrLineTooLong</span> = <a href="https://golang.org/pkg/net/http/internal/">internal</a>.<a href="https://golang.org/pkg/net/http/internal/#ErrLineTooLong">ErrLineTooLong</a></pre>
+                               <p>
+ErrLineTooLong is returned when reading request or response bodies
+with malformed chunked encoding.
+</p>
+
+                       
+                               <pre>var <span id="ErrMissingFile">ErrMissingFile</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: no such file")</pre>
+                               <p>
+ErrMissingFile is returned by FormFile when the provided file field name
+is either not present in the request or not a file field.
+</p>
+
+                       
+                               <pre>var <span id="ErrNoCookie">ErrNoCookie</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: named cookie not present")</pre>
+                               <p>
+ErrNoCookie is returned by Request's Cookie method when a cookie is not found.
+</p>
+
+                       
+                               <pre>var <span id="ErrNoLocation">ErrNoLocation</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: no Location header in response")</pre>
+                               <p>
+ErrNoLocation is returned by Response's Location method
+when no Location header is present.
+</p>
+
+                       
+               
+               
+                       
+                       
+                       <h2 id="CanonicalHeaderKey">func <a href="https://golang.org/src/net/http/header.go?s=4562:4602#L163">CanonicalHeaderKey</a></h2>
+                       <pre>func CanonicalHeaderKey(s <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                       <p>
+CanonicalHeaderKey returns the canonical format of the
+header key s.  The canonicalization converts the first
+letter and any letter following a hyphen to upper case;
+the rest are converted to lowercase.  For example, the
+canonical key for "accept-encoding" is "Accept-Encoding".
+If s contains a space or invalid header field bytes, it is
+returned without modifications.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="DetectContentType">func <a href="https://golang.org/src/net/http/sniff.go?s=648:690#L11">DetectContentType</a></h2>
+                       <pre>func DetectContentType(data []<a href="https://golang.org/pkg/builtin/#byte">byte</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                       <p>
+DetectContentType implements the algorithm described
+at <a href="http://mimesniff.spec.whatwg.org/">http://mimesniff.spec.whatwg.org/</a> to determine the
+Content-Type of the given data.  It considers at most the
+first 512 bytes of data.  DetectContentType always returns
+a valid MIME type: if it cannot determine a more specific one, it
+returns "application/octet-stream".
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="Error">func <a href="https://golang.org/src/net/http/server.go?s=41562:41614#L1419">Error</a></h2>
+                       <pre>func Error(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, error <a href="https://golang.org/pkg/builtin/#string">string</a>, code <a href="https://golang.org/pkg/builtin/#int">int</a>)</pre>
+                       <p>
+Error replies to the request with the specified error message and HTTP code.
+The error message should be plain text.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="Handle">func <a href="https://golang.org/src/net/http/server.go?s=51434:51478#L1740">Handle</a></h2>
+                       <pre>func Handle(pattern <a href="https://golang.org/pkg/builtin/#string">string</a>, handler <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>)</pre>
+                       <p>
+Handle registers the handler for the given pattern
+in the DefaultServeMux.
+The documentation for ServeMux explains how patterns are matched.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="HandleFunc">func <a href="https://golang.org/src/net/http/server.go?s=51688:51759#L1745">HandleFunc</a></h2>
+                       <pre>func HandleFunc(pattern <a href="https://golang.org/pkg/builtin/#string">string</a>, handler func(<a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, *<a href="https://golang.org/pkg/net/http/#Request">Request</a>))</pre>
+                       <p>
+HandleFunc registers the handler function for the given pattern
+in the DefaultServeMux.
+The documentation for ServeMux explains how patterns are matched.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="ListenAndServe">func <a href="https://golang.org/src/net/http/server.go?s=58314:58369#L1955">ListenAndServe</a></h2>
+                       <pre>func ListenAndServe(addr <a href="https://golang.org/pkg/builtin/#string">string</a>, handler <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                       <p>
+ListenAndServe listens on the TCP network address addr
+and then calls Serve with handler to handle requests
+on incoming connections.  Handler is typically nil,
+in which case the DefaultServeMux is used.
+</p>
+<p>
+A trivial example server is:
+</p>
+<pre>package main
+
+import (
+       "io"
+       "net/http"
+       "log"
+)
+
+// hello world, the web server
+func HelloServer(w http.ResponseWriter, req *http.Request) {
+       io.WriteString(w, "hello, world!\n")
+}
+
+func main() {
+       http.HandleFunc("/hello", HelloServer)
+       err := http.ListenAndServe(":12345", nil)
+       if err != nil {
+               log.Fatal("ListenAndServe: ", err)
+       }
+}
+</pre>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="ListenAndServeTLS">func <a href="https://golang.org/src/net/http/server.go?s=59426:59517#L1988">ListenAndServeTLS</a></h2>
+                       <pre>func ListenAndServeTLS(addr <a href="https://golang.org/pkg/builtin/#string">string</a>, certFile <a href="https://golang.org/pkg/builtin/#string">string</a>, keyFile <a href="https://golang.org/pkg/builtin/#string">string</a>, handler <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                       <p>
+ListenAndServeTLS acts identically to ListenAndServe, except that it
+expects HTTPS connections. Additionally, files containing a certificate and
+matching private key for the server must be provided. If the certificate
+is signed by a certificate authority, the certFile should be the concatenation
+of the server's certificate, any intermediates, and the CA's certificate.
+</p>
+<p>
+A trivial example server is:
+</p>
+<pre>import (
+       "log"
+       "net/http"
+)
+
+func handler(w http.ResponseWriter, req *http.Request) {
+       w.Header().Set("Content-Type", "text/plain")
+       w.Write([]byte("This is an example server.\n"))
+}
+
+func main() {
+       http.HandleFunc("/", handler)
+       log.Printf("About to listen on 10443. Go to <a href="https://127.0.0.1:10443/">https://127.0.0.1:10443/</a>")
+       err := http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil)
+       if err != nil {
+               log.Fatal(err)
+       }
+}
+</pre>
+<p>
+One can use generate_cert.go in crypto/tls to generate cert.pem and key.pem.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="MaxBytesReader">func <a href="https://golang.org/src/net/http/request.go?s=22939:23016#L726">MaxBytesReader</a></h2>
+                       <pre>func MaxBytesReader(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#ReadCloser">ReadCloser</a>, n <a href="https://golang.org/pkg/builtin/#int64">int64</a>) <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#ReadCloser">ReadCloser</a></pre>
+                       <p>
+MaxBytesReader is similar to io.LimitReader but is intended for
+limiting the size of incoming request bodies. In contrast to
+io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a
+non-EOF error for a Read beyond the limit, and closes the
+underlying reader when its Close method is called.
+</p>
+<p>
+MaxBytesReader prevents clients from accidentally or maliciously
+sending a large request and wasting server resources.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="NotFound">func <a href="https://golang.org/src/net/http/server.go?s=41848:41891#L1427">NotFound</a></h2>
+                       <pre>func NotFound(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)</pre>
+                       <p>
+NotFound replies to the request with an HTTP 404 not found error.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="ParseHTTPVersion">func <a href="https://golang.org/src/net/http/request.go?s=16539:16601#L498">ParseHTTPVersion</a></h2>
+                       <pre>func ParseHTTPVersion(vers <a href="https://golang.org/pkg/builtin/#string">string</a>) (major, minor <a href="https://golang.org/pkg/builtin/#int">int</a>, ok <a href="https://golang.org/pkg/builtin/#bool">bool</a>)</pre>
+                       <p>
+ParseHTTPVersion parses a HTTP version string.
+"HTTP/1.0" returns (1, 0, true).
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="ParseTime">func <a href="https://golang.org/src/net/http/header.go?s=1908:1960#L69">ParseTime</a></h2>
+                       <pre>func ParseTime(text <a href="https://golang.org/pkg/builtin/#string">string</a>) (t <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Time">Time</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                       <p>
+ParseTime parses a time header (such as the Date: header),
+trying each of the three formats allowed by HTTP/1.1:
+TimeFormat, time.RFC850, and time.ANSIC.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="ProxyFromEnvironment">func <a href="https://golang.org/src/net/http/transport.go?s=4805:4862#L126">ProxyFromEnvironment</a></h2>
+                       <pre>func ProxyFromEnvironment(req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (*<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                       <p>
+ProxyFromEnvironment returns the URL of the proxy to use for a
+given request, as indicated by the environment variables
+HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions
+thereof). HTTPS_PROXY takes precedence over HTTP_PROXY for https
+requests.
+</p>
+<p>
+The environment values may be either a complete URL or a
+"host[:port]", in which case the "http" scheme is assumed.
+An error is returned if the value is a different form.
+</p>
+<p>
+A nil URL and nil error are returned if no proxy is defined in the
+environment, or a proxy should not be used for the given request,
+as defined by NO_PROXY.
+</p>
+<p>
+As a special case, if req.URL.Host is "localhost" (with or without
+a port number), then a nil URL and nil error will be returned.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="ProxyURL">func <a href="https://golang.org/src/net/http/transport.go?s=5664:5729#L157">ProxyURL</a></h2>
+                       <pre>func ProxyURL(fixedURL *<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>) func(*<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (*<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                       <p>
+ProxyURL returns a proxy function (for use in a Transport)
+that always returns the same URL.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="Redirect">func <a href="https://golang.org/src/net/http/server.go?s=42815:42883#L1454">Redirect</a></h2>
+                       <pre>func Redirect(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>, urlStr <a href="https://golang.org/pkg/builtin/#string">string</a>, code <a href="https://golang.org/pkg/builtin/#int">int</a>)</pre>
+                       <p>
+Redirect replies to the request with a redirect to url,
+which may be a path relative to the request path.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="Serve">func <a href="https://golang.org/src/net/http/server.go?s=52072:52121#L1753">Serve</a></h2>
+                       <pre>func Serve(l <a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Listener">Listener</a>, handler <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                       <p>
+Serve accepts incoming HTTP connections on the listener l,
+creating a new service goroutine for each.  The service goroutines
+read requests and then call handler to reply to them.
+Handler is typically nil, in which case the DefaultServeMux is used.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="ServeContent">func <a href="https://golang.org/src/net/http/fs.go?s=3535:3639#L107">ServeContent</a></h2>
+                       <pre>func ServeContent(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>, name <a href="https://golang.org/pkg/builtin/#string">string</a>, modtime <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Time">Time</a>, content <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#ReadSeeker">ReadSeeker</a>)</pre>
+                       <p>
+ServeContent replies to the request using the content in the
+provided ReadSeeker.  The main benefit of ServeContent over io.Copy
+is that it handles Range requests properly, sets the MIME type, and
+handles If-Modified-Since requests.
+</p>
+<p>
+If the response's Content-Type header is not set, ServeContent
+first tries to deduce the type from name's file extension and,
+if that fails, falls back to reading the first block of the content
+and passing it to DetectContentType.
+The name is otherwise unused; in particular it can be empty and is
+never sent in the response.
+</p>
+<p>
+If modtime is not the zero time or Unix epoch, ServeContent
+includes it in a Last-Modified header in the response.  If the
+request includes an If-Modified-Since header, ServeContent uses
+modtime to decide whether the content needs to be sent at all.
+</p>
+<p>
+The content's Seek method must work: ServeContent uses
+a seek to the end of the content to determine its size.
+</p>
+<p>
+If the caller has set w's ETag header, ServeContent uses it to
+handle requests using If-Range and If-None-Match.
+</p>
+<p>
+Note that *os.File implements the io.ReadSeeker interface.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="ServeFile">func <a href="https://golang.org/src/net/http/fs.go?s=13941:13998#L443">ServeFile</a></h2>
+                       <pre>func ServeFile(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>, name <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+                       <p>
+ServeFile replies to the request with the contents of the named
+file or directory.
+</p>
+<p>
+As a special case, ServeFile redirects any request where r.URL.Path
+ends in "/index.html" to the same path, without the final
+"index.html". To avoid such redirects either modify the path or
+use ServeContent.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="SetCookie">func <a href="https://golang.org/src/net/http/cookie.go?s=3059:3107#L120">SetCookie</a></h2>
+                       <pre>func SetCookie(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, cookie *<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>)</pre>
+                       <p>
+SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
+The provided cookie must have a valid Name. Invalid cookies may be
+silently dropped.
+</p>
+
+                       
+                       
+
+               
+                       
+                       
+                       <h2 id="StatusText">func <a href="https://golang.org/src/net/http/status.go?s=4602:4634#L108">StatusText</a></h2>
+                       <pre>func StatusText(code <a href="https://golang.org/pkg/builtin/#int">int</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                       <p>
+StatusText returns a text for the HTTP status code. It returns the empty
+string if the code is unknown.
+</p>
+
+                       
+                       
+
+               
+               
+                       
+                       
+                       <h2 id="Client">type <a href="https://golang.org/src/net/http/client.go?s=897:2420#L26">Client</a></h2>
+                       <pre>type Client struct {
+        <span class="comment">// Transport specifies the mechanism by which individual</span>
+        <span class="comment">// HTTP requests are made.</span>
+        <span class="comment">// If nil, DefaultTransport is used.</span>
+        Transport <a href="https://golang.org/pkg/net/http/#RoundTripper">RoundTripper</a>
+
+        <span class="comment">// CheckRedirect specifies the policy for handling redirects.</span>
+        <span class="comment">// If CheckRedirect is not nil, the client calls it before</span>
+        <span class="comment">// following an HTTP redirect. The arguments req and via are</span>
+        <span class="comment">// the upcoming request and the requests made already, oldest</span>
+        <span class="comment">// first. If CheckRedirect returns an error, the Client's Get</span>
+        <span class="comment">// method returns both the previous Response and</span>
+        <span class="comment">// CheckRedirect's error (wrapped in a url.Error) instead of</span>
+        <span class="comment">// issuing the Request req.</span>
+        <span class="comment">//</span>
+        <span class="comment">// If CheckRedirect is nil, the Client uses its default policy,</span>
+        <span class="comment">// which is to stop after 10 consecutive requests.</span>
+        CheckRedirect func(req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>, via []*<a href="https://golang.org/pkg/net/http/#Request">Request</a>) <a href="https://golang.org/pkg/builtin/#error">error</a>
+
+        <span class="comment">// Jar specifies the cookie jar.</span>
+        <span class="comment">// If Jar is nil, cookies are not sent in requests and ignored</span>
+        <span class="comment">// in responses.</span>
+        Jar <a href="https://golang.org/pkg/net/http/#CookieJar">CookieJar</a>
+
+        <span class="comment">// Timeout specifies a time limit for requests made by this</span>
+        <span class="comment">// Client. The timeout includes connection time, any</span>
+        <span class="comment">// redirects, and reading the response body. The timer remains</span>
+        <span class="comment">// running after Get, Head, Post, or Do return and will</span>
+        <span class="comment">// interrupt reading of the Response.Body.</span>
+        <span class="comment">//</span>
+        <span class="comment">// A Timeout of zero means no timeout.</span>
+        <span class="comment">//</span>
+        <span class="comment">// The Client's Transport must support the CancelRequest</span>
+        <span class="comment">// method or Client will return errors when attempting to make</span>
+        <span class="comment">// a request with Get, Head, Post, or Do. Client's default</span>
+        <span class="comment">// Transport (DefaultTransport) supports CancelRequest.</span>
+        Timeout <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a>
+}</pre>
+                       <p>
+A Client is an HTTP client. Its zero value (DefaultClient) is a
+usable client that uses DefaultTransport.
+</p>
+<p>
+The Client's Transport typically has internal state (cached TCP
+connections), so Clients should be reused instead of created as
+needed. Clients are safe for concurrent use by multiple goroutines.
+</p>
+<p>
+A Client is higher-level than a RoundTripper (such as Transport)
+and additionally handles HTTP details such as cookies and
+redirects.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+                               
+                               <h3 id="Client.Do">func (*Client) <a href="https://golang.org/src/net/http/client.go?s=6009:6070#L163">Do</a></h3>
+                               <pre>func (c *<a href="https://golang.org/pkg/net/http/#Client">Client</a>) Do(req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+Do sends an HTTP request and returns an HTTP response, following
+policy (e.g. redirects, cookies, auth) as configured on the client.
+</p>
+<p>
+An error is returned if caused by client policy (such as
+CheckRedirect), or if there was an HTTP protocol error.
+A non-2xx response doesn't cause an error.
+</p>
+<p>
+When err is nil, resp always contains a non-nil resp.Body.
+</p>
+<p>
+Callers should close resp.Body when done reading from it. If
+resp.Body is not closed, the Client's underlying RoundTripper
+(typically Transport) may not be able to re-use a persistent TCP
+connection to the server for a subsequent "keep-alive" request.
+</p>
+<p>
+The request Body, if non-nil, will be closed by the underlying
+Transport, even on errors.
+</p>
+<p>
+Generally Get, Post, or PostForm will be used instead of Do.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Client.Get">func (*Client) <a href="https://golang.org/src/net/http/client.go?s=9928:9988#L291">Get</a></h3>
+                               <pre>func (c *<a href="https://golang.org/pkg/net/http/#Client">Client</a>) Get(url <a href="https://golang.org/pkg/builtin/#string">string</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+Get issues a GET to the specified URL. If the response is one of the
+following redirect codes, Get follows the redirect after calling the
+Client's CheckRedirect function:
+</p>
+<pre>301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+</pre>
+<p>
+An error is returned if the Client's CheckRedirect function fails
+or if there was an HTTP protocol error. A non-2xx response doesn't
+cause an error.
+</p>
+<p>
+When err is nil, resp always contains a non-nil resp.Body.
+Caller should close resp.Body when done reading from it.
+</p>
+<p>
+To make a request with custom headers, use NewRequest and Client.Do.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Client.Head">func (*Client) <a href="https://golang.org/src/net/http/client.go?s=16276:16337#L512">Head</a></h3>
+                               <pre>func (c *<a href="https://golang.org/pkg/net/http/#Client">Client</a>) Head(url <a href="https://golang.org/pkg/builtin/#string">string</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+Head issues a HEAD to the specified URL.  If the response is one of the
+following redirect codes, Head follows the redirect after calling the
+Client's CheckRedirect function:
+</p>
+<pre>301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+</pre>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Client.Post">func (*Client) <a href="https://golang.org/src/net/http/client.go?s=14197:14291#L455">Post</a></h3>
+                               <pre>func (c *<a href="https://golang.org/pkg/net/http/#Client">Client</a>) Post(url <a href="https://golang.org/pkg/builtin/#string">string</a>, bodyType <a href="https://golang.org/pkg/builtin/#string">string</a>, body <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Reader">Reader</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+Post issues a POST to the specified URL.
+</p>
+<p>
+Caller should close resp.Body when done reading from it.
+</p>
+<p>
+If the provided body is an io.Closer, it is closed after the
+request.
+</p>
+<p>
+To set custom headers, use NewRequest and Client.Do.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Client.PostForm">func (*Client) <a href="https://golang.org/src/net/http/client.go?s=15401:15483#L486">PostForm</a></h3>
+                               <pre>func (c *<a href="https://golang.org/pkg/net/http/#Client">Client</a>) PostForm(url <a href="https://golang.org/pkg/builtin/#string">string</a>, data <a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#Values">Values</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+PostForm issues a POST to the specified URL,
+with data's keys and values URL-encoded as the request body.
+</p>
+<p>
+The Content-Type header is set to application/x-www-form-urlencoded.
+To set other headers, use NewRequest and DefaultClient.Do.
+</p>
+<p>
+When err is nil, resp always contains a non-nil resp.Body.
+Caller should close resp.Body when done reading from it.
+</p>
+
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="CloseNotifier">type <a href="https://golang.org/src/net/http/server.go?s=3931:4099#L106">CloseNotifier</a></h2>
+                       <pre>type CloseNotifier interface {
+        <span class="comment">// CloseNotify returns a channel that receives a single value</span>
+        <span class="comment">// when the client connection has gone away.</span>
+        CloseNotify() &lt;-chan <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+}</pre>
+                       <p>
+The CloseNotifier interface is implemented by ResponseWriters which
+allow detecting when the underlying connection has gone away.
+</p>
+<p>
+This mechanism can be used to cancel long operations on the server
+if the client has disconnected before the response is ready.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+               
+                       
+                       
+                       <h2 id="ConnState">type <a href="https://golang.org/src/net/http/server.go?s=53915:53933#L1793">ConnState</a></h2>
+                       <pre>type ConnState <a href="https://golang.org/pkg/builtin/#int">int</a></pre>
+                       <p>
+A ConnState represents the state of a client connection to a server.
+It's used by the optional Server.ConnState hook.
+</p>
+
+
+                       
+                               <pre>const (
+        <span class="comment">// StateNew represents a new connection that is expected to</span>
+        <span class="comment">// send a request immediately. Connections begin at this</span>
+        <span class="comment">// state and then transition to either StateActive or</span>
+        <span class="comment">// StateClosed.</span>
+        <span id="StateNew">StateNew</span> <a href="https://golang.org/pkg/net/http/#ConnState">ConnState</a> = <a href="https://golang.org/pkg/builtin/#iota">iota</a>
+
+        <span class="comment">// StateActive represents a connection that has read 1 or more</span>
+        <span class="comment">// bytes of a request. The Server.ConnState hook for</span>
+        <span class="comment">// StateActive fires before the request has entered a handler</span>
+        <span class="comment">// and doesn't fire again until the request has been</span>
+        <span class="comment">// handled. After the request is handled, the state</span>
+        <span class="comment">// transitions to StateClosed, StateHijacked, or StateIdle.</span>
+        <span id="StateActive">StateActive</span>
+
+        <span class="comment">// StateIdle represents a connection that has finished</span>
+        <span class="comment">// handling a request and is in the keep-alive state, waiting</span>
+        <span class="comment">// for a new request. Connections transition from StateIdle</span>
+        <span class="comment">// to either StateActive or StateClosed.</span>
+        <span id="StateIdle">StateIdle</span>
+
+        <span class="comment">// StateHijacked represents a hijacked connection.</span>
+        <span class="comment">// This is a terminal state. It does not transition to StateClosed.</span>
+        <span id="StateHijacked">StateHijacked</span>
+
+        <span class="comment">// StateClosed represents a closed connection.</span>
+        <span class="comment">// This is a terminal state. Hijacked connections do not</span>
+        <span class="comment">// transition to StateClosed.</span>
+        <span id="StateClosed">StateClosed</span>
+)</pre>
+                               
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+                               
+                               <h3 id="ConnState.String">func (ConnState) <a href="https://golang.org/src/net/http/server.go?s=55217:55251#L1834">String</a></h3>
+                               <pre>func (c <a href="https://golang.org/pkg/net/http/#ConnState">ConnState</a>) String() <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                               
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="Cookie">type <a href="https://golang.org/src/net/http/cookie.go?s=439:952#L11">Cookie</a></h2>
+                       <pre>type Cookie struct {
+        Name  <a href="https://golang.org/pkg/builtin/#string">string</a>
+        Value <a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        Path       <a href="https://golang.org/pkg/builtin/#string">string</a>    <span class="comment">// optional</span>
+        Domain     <a href="https://golang.org/pkg/builtin/#string">string</a>    <span class="comment">// optional</span>
+        Expires    <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Time">Time</a> <span class="comment">// optional</span>
+        RawExpires <a href="https://golang.org/pkg/builtin/#string">string</a>    <span class="comment">// for reading cookies only</span>
+
+        <span class="comment">// MaxAge=0 means no 'Max-Age' attribute specified.</span>
+        <span class="comment">// MaxAge&lt;0 means delete cookie now, equivalently 'Max-Age: 0'</span>
+        <span class="comment">// MaxAge&gt;0 means Max-Age attribute present and given in seconds</span>
+        MaxAge   <a href="https://golang.org/pkg/builtin/#int">int</a>
+        Secure   <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+        HttpOnly <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+        Raw      <a href="https://golang.org/pkg/builtin/#string">string</a>
+        Unparsed []<a href="https://golang.org/pkg/builtin/#string">string</a> <span class="comment">// Raw text of unparsed attribute-value pairs</span>
+}</pre>
+                       <p>
+A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
+HTTP response or the Cookie header of an HTTP request.
+</p>
+<p>
+See <a href="http://tools.ietf.org/html/rfc6265">http://tools.ietf.org/html/rfc6265</a> for details.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+                               
+                               <h3 id="Cookie.String">func (*Cookie) <a href="https://golang.org/src/net/http/cookie.go?s=3428:3460#L130">String</a></h3>
+                               <pre>func (c *<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>) String() <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                               <p>
+String returns the serialization of the cookie for use in a Cookie
+header (if only Name and Value are set) or a Set-Cookie response
+header (if other fields are set).
+If c is nil or c.Name is invalid, the empty string is returned.
+</p>
+
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="CookieJar">type <a href="https://golang.org/src/net/http/jar.go?s=433:899#L7">CookieJar</a></h2>
+                       <pre>type CookieJar interface {
+        <span class="comment">// SetCookies handles the receipt of the cookies in a reply for the</span>
+        <span class="comment">// given URL.  It may or may not choose to save the cookies, depending</span>
+        <span class="comment">// on the jar's policy and implementation.</span>
+        SetCookies(u *<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>, cookies []*<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>)
+
+        <span class="comment">// Cookies returns the cookies to send in a request for the given URL.</span>
+        <span class="comment">// It is up to the implementation to honor the standard cookie use</span>
+        <span class="comment">// restrictions such as in RFC 6265.</span>
+        Cookies(u *<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>) []*<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>
+}</pre>
+                       <p>
+A CookieJar manages storage and use of cookies in HTTP requests.
+</p>
+<p>
+Implementations of CookieJar must be safe for concurrent use by multiple
+goroutines.
+</p>
+<p>
+The net/http/cookiejar package provides a CookieJar implementation.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+               
+                       
+                       
+                       <h2 id="Dir">type <a href="https://golang.org/src/net/http/fs.go?s=719:734#L23">Dir</a></h2>
+                       <pre>type Dir <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                       <p>
+A Dir implements FileSystem using the native file system restricted to a
+specific directory tree.
+</p>
+<p>
+While the FileSystem.Open method takes '/'-separated paths, a Dir's string
+value is a filename on the native file system, not a URL, so it is separated
+by filepath.Separator, which isn't necessarily '/'.
+</p>
+<p>
+An empty Dir is treated as ".".
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+                               
+                               <h3 id="Dir.Open">func (Dir) <a href="https://golang.org/src/net/http/fs.go?s=736:780#L25">Open</a></h3>
+                               <pre>func (d <a href="https://golang.org/pkg/net/http/#Dir">Dir</a>) Open(name <a href="https://golang.org/pkg/builtin/#string">string</a>) (<a href="https://golang.org/pkg/net/http/#File">File</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="File">type <a href="https://golang.org/src/net/http/fs.go?s=1591:1755#L52">File</a></h2>
+                       <pre>type File interface {
+        <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Closer">Closer</a>
+        <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Reader">Reader</a>
+        Readdir(count <a href="https://golang.org/pkg/builtin/#int">int</a>) ([]<a href="https://golang.org/pkg/os/">os</a>.<a href="https://golang.org/pkg/os/#FileInfo">FileInfo</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+        Seek(offset <a href="https://golang.org/pkg/builtin/#int64">int64</a>, whence <a href="https://golang.org/pkg/builtin/#int">int</a>) (<a href="https://golang.org/pkg/builtin/#int64">int64</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+        Stat() (<a href="https://golang.org/pkg/os/">os</a>.<a href="https://golang.org/pkg/os/#FileInfo">FileInfo</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+}</pre>
+                       <p>
+A File is returned by a FileSystem's Open method and can be
+served by the FileServer implementation.
+</p>
+<p>
+The methods should behave the same as those on an *os.File.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+               
+                       
+                       
+                       <h2 id="FileSystem">type <a href="https://golang.org/src/net/http/fs.go?s=1354:1416#L44">FileSystem</a></h2>
+                       <pre>type FileSystem interface {
+        Open(name <a href="https://golang.org/pkg/builtin/#string">string</a>) (<a href="https://golang.org/pkg/net/http/#File">File</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+}</pre>
+                       <p>
+A FileSystem implements access to a collection of named files.
+The elements in a file path are separated by slash ('/', U+002F)
+characters, regardless of host operating system convention.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+               
+                       
+                       
+                       <h2 id="Flusher">type <a href="https://golang.org/src/net/http/server.go?s=2921:3005#L79">Flusher</a></h2>
+                       <pre>type Flusher interface {
+        <span class="comment">// Flush sends any buffered data to the client.</span>
+        Flush()
+}</pre>
+                       <p>
+The Flusher interface is implemented by ResponseWriters that allow
+an HTTP handler to flush buffered data to the client.
+</p>
+<p>
+Note that even for ResponseWriters that support Flush,
+if the client is connected through an HTTP proxy,
+the buffered data may not reach the client until the response
+completes.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+               
+                       
+                       
+                       <h2 id="Handler">type <a href="https://golang.org/src/net/http/server.go?s=1361:1424#L42">Handler</a></h2>
+                       <pre>type Handler interface {
+        ServeHTTP(<a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)
+}</pre>
+                       <p>
+Objects implementing the Handler interface can be
+registered to serve a particular path or subtree
+in the HTTP server.
+</p>
+<p>
+ServeHTTP should write reply headers and data to the ResponseWriter
+and then return.  Returning signals that the request is finished
+and that the HTTP server can move on to the next request on
+the connection.
+</p>
+<p>
+If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
+that the effect of the panic was isolated to the active request.
+It recovers the panic, logs a stack trace to the server error log,
+and hangs up the connection.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+                               
+                               <h3 id="FileServer">func <a href="https://golang.org/src/net/http/fs.go?s=14534:14574#L463">FileServer</a></h3>
+                               <pre>func FileServer(root <a href="https://golang.org/pkg/net/http/#FileSystem">FileSystem</a>) <a href="https://golang.org/pkg/net/http/#Handler">Handler</a></pre>
+                               <p>
+FileServer returns a handler that serves HTTP requests
+with the contents of the file system rooted at root.
+</p>
+<p>
+To use the operating system's file system implementation,
+use http.Dir:
+</p>
+<pre>http.Handle("/", http.FileServer(http.Dir("/tmp")))
+</pre>
+<p>
+As a special case, the returned file server redirects any request
+ending in "/index.html" to the same path, without the final
+"index.html".
+</p>
+
+                               <div id="example_FileServer" class="toggle">
+       <div class="collapsed">
+               <p class="exampleHeading toggleButton">▹ <span class="text">Example</span></p>
+       </div>
+       <div class="expanded">
+               <p class="exampleHeading toggleButton">▾ <span class="text">Example</span></p>
+               
+               
+               
+                       <div class="play">
+                               <div class="input"><textarea class="code">package main
+
+import (
+       "log"
+       "net/http"
+)
+
+func main() {
+       // Simple static webserver:
+       log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
+}
+</textarea></div>
+                               <div class="output"><pre></pre></div>
+                               <div class="buttons">
+                                       <a class="run" title="Run this code [shift-enter]">Run</a>
+                                       <a class="fmt" title="Format this code">Format</a>
+                                       
+                               </div>
+                       </div>
+               
+       </div>
+</div>
+<div id="example_FileServer_stripPrefix" class="toggle">
+       <div class="collapsed">
+               <p class="exampleHeading toggleButton">▹ <span class="text">Example (StripPrefix)</span></p>
+       </div>
+       <div class="expanded">
+               <p class="exampleHeading toggleButton">▾ <span class="text">Example (StripPrefix)</span></p>
+               
+               
+               
+                       <div class="play">
+                               <div class="input"><textarea class="code">package main
+
+import (
+       "net/http"
+)
+
+func main() {
+       // To serve a directory on disk (/tmp) under an alternate URL
+       // path (/tmpfiles/), use StripPrefix to modify the request
+       // URL's path before the FileServer sees it:
+       http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
+}
+</textarea></div>
+                               <div class="output"><pre></pre></div>
+                               <div class="buttons">
+                                       <a class="run" title="Run this code [shift-enter]">Run</a>
+                                       <a class="fmt" title="Format this code">Format</a>
+                                       
+                               </div>
+                       </div>
+               
+       </div>
+</div>
+
+                               
+                       
+                               
+                               <h3 id="NotFoundHandler">func <a href="https://golang.org/src/net/http/server.go?s=42065:42095#L1431">NotFoundHandler</a></h3>
+                               <pre>func NotFoundHandler() <a href="https://golang.org/pkg/net/http/#Handler">Handler</a></pre>
+                               <p>
+NotFoundHandler returns a simple request handler
+that replies to each request with a “404 page not found” reply.
+</p>
+
+                               
+                               
+                       
+                               
+                               <h3 id="RedirectHandler">func <a href="https://golang.org/src/net/http/server.go?s=45249:45299#L1538">RedirectHandler</a></h3>
+                               <pre>func RedirectHandler(url <a href="https://golang.org/pkg/builtin/#string">string</a>, code <a href="https://golang.org/pkg/builtin/#int">int</a>) <a href="https://golang.org/pkg/net/http/#Handler">Handler</a></pre>
+                               <p>
+RedirectHandler returns a request handler that redirects
+each request it receives to the given url using the given
+status code.
+</p>
+
+                               
+                               
+                       
+                               
+                               <h3 id="StripPrefix">func <a href="https://golang.org/src/net/http/server.go?s=42404:42454#L1438">StripPrefix</a></h3>
+                               <pre>func StripPrefix(prefix <a href="https://golang.org/pkg/builtin/#string">string</a>, h <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>) <a href="https://golang.org/pkg/net/http/#Handler">Handler</a></pre>
+                               <p>
+StripPrefix returns a handler that serves HTTP requests
+by removing the given prefix from the request URL's Path
+and invoking the handler h. StripPrefix handles a
+request for a path that doesn't begin with prefix by
+replying with an HTTP 404 not found error.
+</p>
+
+                               <div id="example_StripPrefix" class="toggle">
+       <div class="collapsed">
+               <p class="exampleHeading toggleButton">▹ <span class="text">Example</span></p>
+       </div>
+       <div class="expanded">
+               <p class="exampleHeading toggleButton">▾ <span class="text">Example</span></p>
+               
+               
+               
+                       <div class="play">
+                               <div class="input"><textarea class="code">package main
+
+import (
+       "net/http"
+)
+
+func main() {
+       // To serve a directory on disk (/tmp) under an alternate URL
+       // path (/tmpfiles/), use StripPrefix to modify the request
+       // URL's path before the FileServer sees it:
+       http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
+}
+</textarea></div>
+                               <div class="output"><pre></pre></div>
+                               <div class="buttons">
+                                       <a class="run" title="Run this code [shift-enter]">Run</a>
+                                       <a class="fmt" title="Format this code">Format</a>
+                                       
+                               </div>
+                       </div>
+               
+       </div>
+</div>
+
+                               
+                       
+                               
+                               <h3 id="TimeoutHandler">func <a href="https://golang.org/src/net/http/server.go?s=61275:61343#L2039">TimeoutHandler</a></h3>
+                               <pre>func TimeoutHandler(h <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>, dt <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a>, msg <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/net/http/#Handler">Handler</a></pre>
+                               <p>
+TimeoutHandler returns a Handler that runs h with the given time limit.
+</p>
+<p>
+The new Handler calls h.ServeHTTP to handle each request, but if a
+call runs for longer than its time limit, the handler responds with
+a 503 Service Unavailable error and the given message in its body.
+(If msg is empty, a suitable default message will be sent.)
+After such a timeout, writes by h to its ResponseWriter will return
+ErrHandlerTimeout.
+</p>
+
+                               
+                               
+                       
+
+                       
+               
+                       
+                       
+                       <h2 id="HandlerFunc">type <a href="https://golang.org/src/net/http/server.go?s=41267:41314#L1408">HandlerFunc</a></h2>
+                       <pre>type HandlerFunc func(<a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)</pre>
+                       <p>
+The HandlerFunc type is an adapter to allow the use of
+ordinary functions as HTTP handlers.  If f is a function
+with the appropriate signature, HandlerFunc(f) is a
+Handler object that calls f.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+                               
+                               <h3 id="HandlerFunc.ServeHTTP">func (HandlerFunc) <a href="https://golang.org/src/net/http/server.go?s=41344:41404#L1411">ServeHTTP</a></h3>
+                               <pre>func (f <a href="https://golang.org/pkg/net/http/#HandlerFunc">HandlerFunc</a>) ServeHTTP(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)</pre>
+                               <p>
+ServeHTTP calls f(w, r).
+</p>
+
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="Header">type <a href="https://golang.org/src/net/http/header.go?s=350:381#L9">Header</a></h2>
+                       <pre>type Header map[<a href="https://golang.org/pkg/builtin/#string">string</a>][]<a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                       <p>
+A Header represents the key-value pairs in an HTTP header.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+                               
+                               <h3 id="Header.Add">func (Header) <a href="https://golang.org/src/net/http/header.go?s=488:526#L13">Add</a></h3>
+                               <pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) Add(key, value <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+                               <p>
+Add adds the key, value pair to the header.
+It appends to any existing values associated with key.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Header.Del">func (Header) <a href="https://golang.org/src/net/http/header.go?s=1321:1352#L41">Del</a></h3>
+                               <pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) Del(key <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+                               <p>
+Del deletes the values associated with key.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Header.Get">func (Header) <a href="https://golang.org/src/net/http/header.go?s=1015:1053#L28">Get</a></h3>
+                               <pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) Get(key <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                               <p>
+Get gets the first value associated with the given key.
+If there are no values associated with the key, Get returns "".
+To access multiple values of a key, access the map directly
+with CanonicalHeaderKey.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Header.Set">func (Header) <a href="https://golang.org/src/net/http/header.go?s=713:751#L20">Set</a></h3>
+                               <pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) Set(key, value <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+                               <p>
+Set sets the header entries associated with key to
+the single element value.  It replaces any existing
+values associated with key.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Header.Write">func (Header) <a href="https://golang.org/src/net/http/header.go?s=1433:1473#L46">Write</a></h3>
+                               <pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) Write(w <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Writer">Writer</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                               <p>
+Write writes a header in wire format.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Header.WriteSubset">func (Header) <a href="https://golang.org/src/net/http/header.go?s=3676:3747#L135">WriteSubset</a></h3>
+                               <pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) WriteSubset(w <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Writer">Writer</a>, exclude map[<a href="https://golang.org/pkg/builtin/#string">string</a>]<a href="https://golang.org/pkg/builtin/#bool">bool</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                               <p>
+WriteSubset writes a header in wire format.
+If exclude is not nil, keys where exclude[key] == true are not written.
+</p>
+
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="Hijacker">type <a href="https://golang.org/src/net/http/server.go?s=3126:3656#L86">Hijacker</a></h2>
+                       <pre>type Hijacker interface {
+        <span class="comment">// Hijack lets the caller take over the connection.</span>
+        <span class="comment">// After a call to Hijack(), the HTTP server library</span>
+        <span class="comment">// will not do anything else with the connection.</span>
+        <span class="comment">//</span>
+        <span class="comment">// It becomes the caller's responsibility to manage</span>
+        <span class="comment">// and close the connection.</span>
+        <span class="comment">//</span>
+        <span class="comment">// The returned net.Conn may have read or write deadlines</span>
+        <span class="comment">// already set, depending on the configuration of the</span>
+        <span class="comment">// Server. It is the caller's responsibility to set</span>
+        <span class="comment">// or clear those deadlines as needed.</span>
+        Hijack() (<a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Conn">Conn</a>, *<a href="https://golang.org/pkg/bufio/">bufio</a>.<a href="https://golang.org/pkg/bufio/#ReadWriter">ReadWriter</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+}</pre>
+                       <p>
+The Hijacker interface is implemented by ResponseWriters that allow
+an HTTP handler to take over the connection.
+</p>
+
+
+                       
+
+                       
+
+                       <div id="example_Hijacker" class="toggle">
+       <div class="collapsed">
+               <p class="exampleHeading toggleButton">▹ <span class="text">Example</span></p>
+       </div>
+       <div class="expanded">
+               <p class="exampleHeading toggleButton">▾ <span class="text">Example</span></p>
+               
+               
+               
+                       <div class="play">
+                               <div class="input"><textarea class="code">package main
+
+import (
+       "fmt"
+       "log"
+       "net/http"
+)
+
+func main() {
+       http.HandleFunc("/hijack", func(w http.ResponseWriter, r *http.Request) {
+               hj, ok := w.(http.Hijacker)
+               if !ok {
+                       http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
+                       return
+               }
+               conn, bufrw, err := hj.Hijack()
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+               // Don't forget to close the connection:
+               defer conn.Close()
+               bufrw.WriteString("Now we're speaking raw TCP. Say hi: ")
+               bufrw.Flush()
+               s, err := bufrw.ReadString('\n')
+               if err != nil {
+                       log.Printf("error reading string: %v", err)
+                       return
+               }
+               fmt.Fprintf(bufrw, "You said: %q\nBye.\n", s)
+               bufrw.Flush()
+       })
+}
+</textarea></div>
+                               <div class="output"><pre></pre></div>
+                               <div class="buttons">
+                                       <a class="run" title="Run this code [shift-enter]">Run</a>
+                                       <a class="fmt" title="Format this code">Format</a>
+                                       
+                               </div>
+                       </div>
+               
+       </div>
+</div>
+
+                       
+                       
+
+                       
+
+                       
+               
+                       
+                       
+                       <h2 id="ProtocolError">type <a href="https://golang.org/src/net/http/request.go?s=668:717#L26">ProtocolError</a></h2>
+                       <pre>type ProtocolError struct {
+        ErrorString <a href="https://golang.org/pkg/builtin/#string">string</a>
+}</pre>
+                       <p>
+HTTP request parsing errors.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+                               
+                               <h3 id="ProtocolError.Error">func (*ProtocolError) <a href="https://golang.org/src/net/http/request.go?s=719:759#L30">Error</a></h3>
+                               <pre>func (err *<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>) Error() <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                               
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="Request">type <a href="https://golang.org/src/net/http/request.go?s=2057:8270#L64">Request</a></h2>
+                       <pre>type Request struct {
+        <span class="comment">// Method specifies the HTTP method (GET, POST, PUT, etc.).</span>
+        <span class="comment">// For client requests an empty string means GET.</span>
+        Method <a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// URL specifies either the URI being requested (for server</span>
+        <span class="comment">// requests) or the URL to access (for client requests).</span>
+        <span class="comment">//</span>
+        <span class="comment">// For server requests the URL is parsed from the URI</span>
+        <span class="comment">// supplied on the Request-Line as stored in RequestURI.  For</span>
+        <span class="comment">// most requests, fields other than Path and RawQuery will be</span>
+        <span class="comment">// empty. (See RFC 2616, Section 5.1.2)</span>
+        <span class="comment">//</span>
+        <span class="comment">// For client requests, the URL's Host specifies the server to</span>
+        <span class="comment">// connect to, while the Request's Host field optionally</span>
+        <span class="comment">// specifies the Host header value to send in the HTTP</span>
+        <span class="comment">// request.</span>
+        URL *<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>
+
+        <span class="comment">// The protocol version for incoming requests.</span>
+        <span class="comment">// Client requests always use HTTP/1.1.</span>
+        Proto      <a href="https://golang.org/pkg/builtin/#string">string</a> <span class="comment">// "HTTP/1.0"</span>
+        ProtoMajor <a href="https://golang.org/pkg/builtin/#int">int</a>    <span class="comment">// 1</span>
+        ProtoMinor <a href="https://golang.org/pkg/builtin/#int">int</a>    <span class="comment">// 0</span>
+
+        <span class="comment">// A header maps request lines to their values.</span>
+        <span class="comment">// If the header says</span>
+        <span class="comment">//</span>
+        <span class="comment">//       accept-encoding: gzip, deflate</span>
+        <span class="comment">//       Accept-Language: en-us</span>
+        <span class="comment">//       Connection: keep-alive</span>
+        <span class="comment">//</span>
+        <span class="comment">// then</span>
+        <span class="comment">//</span>
+        <span class="comment">//       Header = map[string][]string{</span>
+        <span class="comment">//               "Accept-Encoding": {"gzip, deflate"},</span>
+        <span class="comment">//               "Accept-Language": {"en-us"},</span>
+        <span class="comment">//               "Connection": {"keep-alive"},</span>
+        <span class="comment">//       }</span>
+        <span class="comment">//</span>
+        <span class="comment">// HTTP defines that header names are case-insensitive.</span>
+        <span class="comment">// The request parser implements this by canonicalizing the</span>
+        <span class="comment">// name, making the first character and any characters</span>
+        <span class="comment">// following a hyphen uppercase and the rest lowercase.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For client requests certain headers are automatically</span>
+        <span class="comment">// added and may override values in Header.</span>
+        <span class="comment">//</span>
+        <span class="comment">// See the documentation for the Request.Write method.</span>
+        Header <a href="https://golang.org/pkg/net/http/#Header">Header</a>
+
+        <span class="comment">// Body is the request's body.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For client requests a nil body means the request has no</span>
+        <span class="comment">// body, such as a GET request. The HTTP Client's Transport</span>
+        <span class="comment">// is responsible for calling the Close method.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For server requests the Request Body is always non-nil</span>
+        <span class="comment">// but will return EOF immediately when no body is present.</span>
+        <span class="comment">// The Server will close the request body. The ServeHTTP</span>
+        <span class="comment">// Handler does not need to.</span>
+        Body <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#ReadCloser">ReadCloser</a>
+
+        <span class="comment">// ContentLength records the length of the associated content.</span>
+        <span class="comment">// The value -1 indicates that the length is unknown.</span>
+        <span class="comment">// Values &gt;= 0 indicate that the given number of bytes may</span>
+        <span class="comment">// be read from Body.</span>
+        <span class="comment">// For client requests, a value of 0 means unknown if Body is not nil.</span>
+        ContentLength <a href="https://golang.org/pkg/builtin/#int64">int64</a>
+
+        <span class="comment">// TransferEncoding lists the transfer encodings from outermost to</span>
+        <span class="comment">// innermost. An empty list denotes the "identity" encoding.</span>
+        <span class="comment">// TransferEncoding can usually be ignored; chunked encoding is</span>
+        <span class="comment">// automatically added and removed as necessary when sending and</span>
+        <span class="comment">// receiving requests.</span>
+        TransferEncoding []<a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// Close indicates whether to close the connection after</span>
+        <span class="comment">// replying to this request (for servers) or after sending</span>
+        <span class="comment">// the request (for clients).</span>
+        Close <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+
+        <span class="comment">// For server requests Host specifies the host on which the</span>
+        <span class="comment">// URL is sought. Per RFC 2616, this is either the value of</span>
+        <span class="comment">// the "Host" header or the host name given in the URL itself.</span>
+        <span class="comment">// It may be of the form "host:port".</span>
+        <span class="comment">//</span>
+        <span class="comment">// For client requests Host optionally overrides the Host</span>
+        <span class="comment">// header to send. If empty, the Request.Write method uses</span>
+        <span class="comment">// the value of URL.Host.</span>
+        Host <a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// Form contains the parsed form data, including both the URL</span>
+        <span class="comment">// field's query parameters and the POST or PUT form data.</span>
+        <span class="comment">// This field is only available after ParseForm is called.</span>
+        <span class="comment">// The HTTP client ignores Form and uses Body instead.</span>
+        Form <a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#Values">Values</a>
+
+        <span class="comment">// PostForm contains the parsed form data from POST, PATCH,</span>
+        <span class="comment">// or PUT body parameters.</span>
+        <span class="comment">//</span>
+        <span class="comment">// This field is only available after ParseForm is called.</span>
+        <span class="comment">// The HTTP client ignores PostForm and uses Body instead.</span>
+        PostForm <a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#Values">Values</a>
+
+        <span class="comment">// MultipartForm is the parsed multipart form, including file uploads.</span>
+        <span class="comment">// This field is only available after ParseMultipartForm is called.</span>
+        <span class="comment">// The HTTP client ignores MultipartForm and uses Body instead.</span>
+        MultipartForm *<a href="https://golang.org/pkg/mime/multipart/">multipart</a>.<a href="https://golang.org/pkg/mime/multipart/#Form">Form</a>
+
+        <span class="comment">// Trailer specifies additional headers that are sent after the request</span>
+        <span class="comment">// body.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For server requests the Trailer map initially contains only the</span>
+        <span class="comment">// trailer keys, with nil values. (The client declares which trailers it</span>
+        <span class="comment">// will later send.)  While the handler is reading from Body, it must</span>
+        <span class="comment">// not reference Trailer. After reading from Body returns EOF, Trailer</span>
+        <span class="comment">// can be read again and will contain non-nil values, if they were sent</span>
+        <span class="comment">// by the client.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For client requests Trailer must be initialized to a map containing</span>
+        <span class="comment">// the trailer keys to later send. The values may be nil or their final</span>
+        <span class="comment">// values. The ContentLength must be 0 or -1, to send a chunked request.</span>
+        <span class="comment">// After the HTTP request is sent the map values can be updated while</span>
+        <span class="comment">// the request body is read. Once the body returns EOF, the caller must</span>
+        <span class="comment">// not mutate Trailer.</span>
+        <span class="comment">//</span>
+        <span class="comment">// Few HTTP clients, servers, or proxies support HTTP trailers.</span>
+        Trailer <a href="https://golang.org/pkg/net/http/#Header">Header</a>
+
+        <span class="comment">// RemoteAddr allows HTTP servers and other software to record</span>
+        <span class="comment">// the network address that sent the request, usually for</span>
+        <span class="comment">// logging. This field is not filled in by ReadRequest and</span>
+        <span class="comment">// has no defined format. The HTTP server in this package</span>
+        <span class="comment">// sets RemoteAddr to an "IP:port" address before invoking a</span>
+        <span class="comment">// handler.</span>
+        <span class="comment">// This field is ignored by the HTTP client.</span>
+        RemoteAddr <a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// RequestURI is the unmodified Request-URI of the</span>
+        <span class="comment">// Request-Line (RFC 2616, Section 5.1) as sent by the client</span>
+        <span class="comment">// to a server. Usually the URL field should be used instead.</span>
+        <span class="comment">// It is an error to set this field in an HTTP client request.</span>
+        RequestURI <a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// TLS allows HTTP servers and other software to record</span>
+        <span class="comment">// information about the TLS connection on which the request</span>
+        <span class="comment">// was received. This field is not filled in by ReadRequest.</span>
+        <span class="comment">// The HTTP server in this package sets the field for</span>
+        <span class="comment">// TLS-enabled connections before invoking a handler;</span>
+        <span class="comment">// otherwise it leaves the field nil.</span>
+        <span class="comment">// This field is ignored by the HTTP client.</span>
+        TLS *<a href="https://golang.org/pkg/crypto/tls/">tls</a>.<a href="https://golang.org/pkg/crypto/tls/#ConnectionState">ConnectionState</a>
+
+        <span class="comment">// Cancel is an optional channel whose closure indicates that the client</span>
+        <span class="comment">// request should be regarded as canceled. Not all implementations of</span>
+        <span class="comment">// RoundTripper may support Cancel.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For server requests, this field is not applicable.</span>
+        Cancel &lt;-chan struct{}
+}</pre>
+                       <p>
+A Request represents an HTTP request received by a server
+or to be sent by a client.
+</p>
+<p>
+The field semantics differ slightly between client and server
+usage. In addition to the notes on the fields below, see the
+documentation for Request.Write and RoundTripper.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+                               
+                               <h3 id="NewRequest">func <a href="https://golang.org/src/net/http/request.go?s=17718:17790#L536">NewRequest</a></h3>
+                               <pre>func NewRequest(method, urlStr <a href="https://golang.org/pkg/builtin/#string">string</a>, body <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Reader">Reader</a>) (*<a href="https://golang.org/pkg/net/http/#Request">Request</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+NewRequest returns a new Request given a method, URL, and optional body.
+</p>
+<p>
+If the provided body is also an io.Closer, the returned
+Request.Body is set to body and will be closed by the Client
+methods Do, Post, and PostForm, and Transport.RoundTrip.
+</p>
+<p>
+NewRequest returns a Request suitable for use with Client.Do or
+Transport.RoundTrip.
+To create a request for use with testing a Server Handler use either
+ReadRequest or manually update the Request fields. See the Request
+type's documentation for the difference between inbound and outbound
+request fields.
+</p>
+
+                               
+                               
+                       
+                               
+                               <h3 id="ReadRequest">func <a href="https://golang.org/src/net/http/request.go?s=20301:20360#L636">ReadRequest</a></h3>
+                               <pre>func ReadRequest(b *<a href="https://golang.org/pkg/bufio/">bufio</a>.<a href="https://golang.org/pkg/bufio/#Reader">Reader</a>) (req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+ReadRequest reads and parses an incoming request from b.
+</p>
+
+                               
+                               
+                       
+
+                       
+                               
+                               <h3 id="Request.AddCookie">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=9423:9461#L259">AddCookie</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) AddCookie(c *<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>)</pre>
+                               <p>
+AddCookie adds a cookie to the request.  Per RFC 6265 section 5.4,
+AddCookie does not attach more than one Cookie header field.  That
+means all cookies, if any, are written into the same line,
+separated by semicolon.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.BasicAuth">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=18567:18633#L572">BasicAuth</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) BasicAuth() (username, password <a href="https://golang.org/pkg/builtin/#string">string</a>, ok <a href="https://golang.org/pkg/builtin/#bool">bool</a>)</pre>
+                               <p>
+BasicAuth returns the username and password provided in the request's
+Authorization header, if the request uses HTTP Basic Authentication.
+See RFC 2617, Section 2.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.Cookie">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=9041:9095#L248">Cookie</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) Cookie(name <a href="https://golang.org/pkg/builtin/#string">string</a>) (*<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+Cookie returns the named cookie provided in the request or
+ErrNoCookie if not found.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.Cookies">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=8727:8764#L239">Cookies</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) Cookies() []*<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a></pre>
+                               <p>
+Cookies parses and returns the HTTP cookies sent with the request.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.FormFile">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=29076:29161#L960">FormFile</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) FormFile(key <a href="https://golang.org/pkg/builtin/#string">string</a>) (<a href="https://golang.org/pkg/mime/multipart/">multipart</a>.<a href="https://golang.org/pkg/mime/multipart/#File">File</a>, *<a href="https://golang.org/pkg/mime/multipart/">multipart</a>.<a href="https://golang.org/pkg/mime/multipart/#FileHeader">FileHeader</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+FormFile returns the first file for the provided form key.
+FormFile calls ParseMultipartForm and ParseForm if necessary.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.FormValue">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=28248:28294#L933">FormValue</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) FormValue(key <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                               <p>
+FormValue returns the first value for the named component of the query.
+POST and PUT body parameters take precedence over URL query string values.
+FormValue calls ParseMultipartForm and ParseForm if necessary and ignores
+any errors returned by these functions.
+If key is not present, FormValue returns the empty string.
+To access multiple values of the same key, call ParseForm and
+then inspect Request.Form directly.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.MultipartReader">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=10760:10822#L292">MultipartReader</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) MultipartReader() (*<a href="https://golang.org/pkg/mime/multipart/">multipart</a>.<a href="https://golang.org/pkg/mime/multipart/#Reader">Reader</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+MultipartReader returns a MIME multipart reader if this is a
+multipart/form-data POST request, else returns nil and an error.
+Use this function instead of ParseMultipartForm to
+process the request body as a stream.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.ParseForm">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=26212:26247#L854">ParseForm</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) ParseForm() <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                               <p>
+ParseForm parses the raw query from the URL and updates r.Form.
+</p>
+<p>
+For POST or PUT requests, it also parses the request body as a form and
+put the results into both r.PostForm and r.Form.
+POST and PUT body parameters take precedence over URL query string values
+in r.Form.
+</p>
+<p>
+If the request Body's size has not already been limited by MaxBytesReader,
+the size is capped at 10MB.
+</p>
+<p>
+ParseMultipartForm calls ParseForm automatically.
+It is idempotent.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.ParseMultipartForm">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=27267:27326#L895">ParseMultipartForm</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) ParseMultipartForm(maxMemory <a href="https://golang.org/pkg/builtin/#int64">int64</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                               <p>
+ParseMultipartForm parses a request body as multipart/form-data.
+The whole request body is parsed and up to a total of maxMemory bytes of
+its file parts are stored in memory, with the remainder stored on
+disk in temporary files.
+ParseMultipartForm calls ParseForm if necessary.
+After one call to ParseMultipartForm, subsequent calls have no effect.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.PostFormValue">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=28755:28805#L948">PostFormValue</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) PostFormValue(key <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                               <p>
+PostFormValue returns the first value for the named component of the POST
+or PUT request body. URL query parameters are ignored.
+PostFormValue calls ParseMultipartForm and ParseForm if necessary and ignores
+any errors returned by these functions.
+If key is not present, PostFormValue returns the empty string.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.ProtoAtLeast">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=8370:8423#L228">ProtoAtLeast</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) ProtoAtLeast(major, minor <a href="https://golang.org/pkg/builtin/#int">int</a>) <a href="https://golang.org/pkg/builtin/#bool">bool</a></pre>
+                               <p>
+ProtoAtLeast reports whether the HTTP protocol used
+in the request is at least major.minor.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.Referer">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=10131:10165#L276">Referer</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) Referer() <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                               <p>
+Referer returns the referring URL, if sent in the request.
+</p>
+<p>
+Referer is misspelled as in the request itself, a mistake from the
+earliest days of HTTP.  This value can also be fetched from the
+Header map as Header["Referer"]; the benefit of making it available
+as a method is that the compiler can diagnose programs that use the
+alternate (correct English) spelling req.Referrer() but cannot
+diagnose programs that use Header["Referrer"].
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.SetBasicAuth">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=19455:19512#L604">SetBasicAuth</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) SetBasicAuth(username, password <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+                               <p>
+SetBasicAuth sets the request's Authorization header to use HTTP
+Basic Authentication with the provided username and password.
+</p>
+<p>
+With HTTP Basic Authentication the provided username and password
+are not encrypted.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.UserAgent">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=8580:8616#L234">UserAgent</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) UserAgent() <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+                               <p>
+UserAgent returns the client's User-Agent, if sent in the request.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.Write">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=12411:12453#L346">Write</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) Write(w <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Writer">Writer</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                               <p>
+Write writes an HTTP/1.1 request, which is the header and body, in wire format.
+This method consults the following fields of the request:
+</p>
+<pre>Host
+URL
+Method (defaults to "GET")
+Header
+ContentLength
+TransferEncoding
+Body
+</pre>
+<p>
+If Body is present, Content-Length is &lt;= 0 and TransferEncoding
+hasn't been set to "identity", Write adds "Transfer-Encoding:
+chunked" to the header. Body is closed after it is sent.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Request.WriteProxy">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=12846:12893#L356">WriteProxy</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) WriteProxy(w <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Writer">Writer</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                               <p>
+WriteProxy is like Write but writes the request in the form
+expected by an HTTP proxy.  In particular, WriteProxy writes the
+initial Request-URI line of the request with an absolute URI, per
+section 5.1.2 of RFC 2616, including the scheme and host.
+In either case, WriteProxy also writes a Host header, using
+either r.Host or r.URL.Host.
+</p>
+
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="Response">type <a href="https://golang.org/src/net/http/response.go?s=512:2849#L19">Response</a></h2>
+                       <pre>type Response struct {
+        Status     <a href="https://golang.org/pkg/builtin/#string">string</a> <span class="comment">// e.g. "200 OK"</span>
+        StatusCode <a href="https://golang.org/pkg/builtin/#int">int</a>    <span class="comment">// e.g. 200</span>
+        Proto      <a href="https://golang.org/pkg/builtin/#string">string</a> <span class="comment">// e.g. "HTTP/1.0"</span>
+        ProtoMajor <a href="https://golang.org/pkg/builtin/#int">int</a>    <span class="comment">// e.g. 1</span>
+        ProtoMinor <a href="https://golang.org/pkg/builtin/#int">int</a>    <span class="comment">// e.g. 0</span>
+
+        <span class="comment">// Header maps header keys to values.  If the response had multiple</span>
+        <span class="comment">// headers with the same key, they may be concatenated, with comma</span>
+        <span class="comment">// delimiters.  (Section 4.2 of RFC 2616 requires that multiple headers</span>
+        <span class="comment">// be semantically equivalent to a comma-delimited sequence.) Values</span>
+        <span class="comment">// duplicated by other fields in this struct (e.g., ContentLength) are</span>
+        <span class="comment">// omitted from Header.</span>
+        <span class="comment">//</span>
+        <span class="comment">// Keys in the map are canonicalized (see CanonicalHeaderKey).</span>
+        Header <a href="https://golang.org/pkg/net/http/#Header">Header</a>
+
+        <span class="comment">// Body represents the response body.</span>
+        <span class="comment">//</span>
+        <span class="comment">// The http Client and Transport guarantee that Body is always</span>
+        <span class="comment">// non-nil, even on responses without a body or responses with</span>
+        <span class="comment">// a zero-length body. It is the caller's responsibility to</span>
+        <span class="comment">// close Body. The default HTTP client's Transport does not</span>
+        <span class="comment">// attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections</span>
+        <span class="comment">// ("keep-alive") unless the Body is read to completion and is</span>
+        <span class="comment">// closed.</span>
+        <span class="comment">//</span>
+        <span class="comment">// The Body is automatically dechunked if the server replied</span>
+        <span class="comment">// with a "chunked" Transfer-Encoding.</span>
+        Body <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#ReadCloser">ReadCloser</a>
+
+        <span class="comment">// ContentLength records the length of the associated content.  The</span>
+        <span class="comment">// value -1 indicates that the length is unknown.  Unless Request.Method</span>
+        <span class="comment">// is "HEAD", values &gt;= 0 indicate that the given number of bytes may</span>
+        <span class="comment">// be read from Body.</span>
+        ContentLength <a href="https://golang.org/pkg/builtin/#int64">int64</a>
+
+        <span class="comment">// Contains transfer encodings from outer-most to inner-most. Value is</span>
+        <span class="comment">// nil, means that "identity" encoding is used.</span>
+        TransferEncoding []<a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// Close records whether the header directed that the connection be</span>
+        <span class="comment">// closed after reading Body.  The value is advice for clients: neither</span>
+        <span class="comment">// ReadResponse nor Response.Write ever closes a connection.</span>
+        Close <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+
+        <span class="comment">// Trailer maps trailer keys to values, in the same</span>
+        <span class="comment">// format as the header.</span>
+        Trailer <a href="https://golang.org/pkg/net/http/#Header">Header</a>
+
+        <span class="comment">// The Request that was sent to obtain this Response.</span>
+        <span class="comment">// Request's Body is nil (having already been consumed).</span>
+        <span class="comment">// This is only populated for Client requests.</span>
+        Request *<a href="https://golang.org/pkg/net/http/#Request">Request</a>
+
+        <span class="comment">// TLS contains information about the TLS connection on which the</span>
+        <span class="comment">// response was received. It is nil for unencrypted responses.</span>
+        <span class="comment">// The pointer is shared between responses and should not be</span>
+        <span class="comment">// modified.</span>
+        TLS *<a href="https://golang.org/pkg/crypto/tls/">tls</a>.<a href="https://golang.org/pkg/crypto/tls/#ConnectionState">ConnectionState</a>
+}</pre>
+                       <p>
+Response represents the response from an HTTP request.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+                               
+                               <h3 id="Get">func <a href="https://golang.org/src/net/http/client.go?s=9198:9246#L270">Get</a></h3>
+                               <pre>func Get(url <a href="https://golang.org/pkg/builtin/#string">string</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+Get issues a GET to the specified URL. If the response is one of
+the following redirect codes, Get follows the redirect, up to a
+maximum of 10 redirects:
+</p>
+<pre>301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+</pre>
+<p>
+An error is returned if there were too many redirects or if there
+was an HTTP protocol error. A non-2xx response doesn't cause an
+error.
+</p>
+<p>
+When err is nil, resp always contains a non-nil resp.Body.
+Caller should close resp.Body when done reading from it.
+</p>
+<p>
+Get is a wrapper around DefaultClient.Get.
+</p>
+<p>
+To make a request with custom headers, use NewRequest and
+DefaultClient.Do.
+</p>
+
+                               <div id="example_Get" class="toggle">
+       <div class="collapsed">
+               <p class="exampleHeading toggleButton">▹ <span class="text">Example</span></p>
+       </div>
+       <div class="expanded">
+               <p class="exampleHeading toggleButton">▾ <span class="text">Example</span></p>
+               
+               
+               
+                       <div class="play">
+                               <div class="input"><textarea class="code">package main
+
+import (
+       "fmt"
+       "io/ioutil"
+       "log"
+       "net/http"
+)
+
+func main() {
+       res, err := http.Get("http://www.google.com/robots.txt")
+       if err != nil {
+               log.Fatal(err)
+       }
+       robots, err := ioutil.ReadAll(res.Body)
+       res.Body.Close()
+       if err != nil {
+               log.Fatal(err)
+       }
+       fmt.Printf("%s", robots)
+}
+</textarea></div>
+                               <div class="output"><pre></pre></div>
+                               <div class="buttons">
+                                       <a class="run" title="Run this code [shift-enter]">Run</a>
+                                       <a class="fmt" title="Format this code">Format</a>
+                                       
+                               </div>
+                       </div>
+               
+       </div>
+</div>
+
+                               
+                       
+                               
+                               <h3 id="Head">func <a href="https://golang.org/src/net/http/client.go?s=15901:15950#L500">Head</a></h3>
+                               <pre>func Head(url <a href="https://golang.org/pkg/builtin/#string">string</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+Head issues a HEAD to the specified URL.  If the response is one of
+the following redirect codes, Head follows the redirect, up to a
+maximum of 10 redirects:
+</p>
+<pre>301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+</pre>
+<p>
+Head is a wrapper around DefaultClient.Head
+</p>
+
+                               
+                               
+                       
+                               
+                               <h3 id="Post">func <a href="https://golang.org/src/net/http/client.go?s=13816:13898#L443">Post</a></h3>
+                               <pre>func Post(url <a href="https://golang.org/pkg/builtin/#string">string</a>, bodyType <a href="https://golang.org/pkg/builtin/#string">string</a>, body <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Reader">Reader</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+Post issues a POST to the specified URL.
+</p>
+<p>
+Caller should close resp.Body when done reading from it.
+</p>
+<p>
+If the provided body is an io.Closer, it is closed after the
+request.
+</p>
+<p>
+Post is a wrapper around DefaultClient.Post.
+</p>
+<p>
+To set custom headers, use NewRequest and DefaultClient.Do.
+</p>
+
+                               
+                               
+                       
+                               
+                               <h3 id="PostForm">func <a href="https://golang.org/src/net/http/client.go?s=14909:14979#L474">PostForm</a></h3>
+                               <pre>func PostForm(url <a href="https://golang.org/pkg/builtin/#string">string</a>, data <a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#Values">Values</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+PostForm issues a POST to the specified URL, with data's keys and
+values URL-encoded as the request body.
+</p>
+<p>
+The Content-Type header is set to application/x-www-form-urlencoded.
+To set other headers, use NewRequest and DefaultClient.Do.
+</p>
+<p>
+When err is nil, resp always contains a non-nil resp.Body.
+Caller should close resp.Body when done reading from it.
+</p>
+<p>
+PostForm is a wrapper around DefaultClient.PostForm.
+</p>
+
+                               
+                               
+                       
+                               
+                               <h3 id="ReadResponse">func <a href="https://golang.org/src/net/http/response.go?s=3992:4059#L111">ReadResponse</a></h3>
+                               <pre>func ReadResponse(r *<a href="https://golang.org/pkg/bufio/">bufio</a>.<a href="https://golang.org/pkg/bufio/#Reader">Reader</a>, req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (*<a href="https://golang.org/pkg/net/http/#Response">Response</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+ReadResponse reads and returns an HTTP response from r.
+The req parameter optionally specifies the Request that corresponds
+to this Response. If nil, a GET request is assumed.
+Clients must call resp.Body.Close when finished reading resp.Body.
+After that call, clients can inspect resp.Trailer to find key/value
+pairs included in the response trailer.
+</p>
+
+                               
+                               
+                       
+
+                       
+                               
+                               <h3 id="Response.Cookies">func (*Response) <a href="https://golang.org/src/net/http/response.go?s=2924:2962#L82">Cookies</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Response">Response</a>) Cookies() []*<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a></pre>
+                               <p>
+Cookies parses and returns the cookies set in the Set-Cookie headers.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Response.Location">func (*Response) <a href="https://golang.org/src/net/http/response.go?s=3387:3434#L94">Location</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Response">Response</a>) Location() (*<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+Location returns the URL of the response's "Location" header,
+if present.  Relative redirects are resolved relative to
+the Response's Request.  ErrNoLocation is returned if no
+Location header is present.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Response.ProtoAtLeast">func (*Response) <a href="https://golang.org/src/net/http/response.go?s=5570:5624#L179">ProtoAtLeast</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Response">Response</a>) ProtoAtLeast(major, minor <a href="https://golang.org/pkg/builtin/#int">int</a>) <a href="https://golang.org/pkg/builtin/#bool">bool</a></pre>
+                               <p>
+ProtoAtLeast reports whether the HTTP protocol used
+in the response is at least major.minor.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Response.Write">func (*Response) <a href="https://golang.org/src/net/http/response.go?s=6162:6205#L200">Write</a></h3>
+                               <pre>func (r *<a href="https://golang.org/pkg/net/http/#Response">Response</a>) Write(w <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Writer">Writer</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                               <p>
+Write writes r to w in the HTTP/1.n server response format,
+including the status line, headers, body, and optional trailer.
+</p>
+<p>
+This method consults the following fields of the response r:
+</p>
+<pre>StatusCode
+ProtoMajor
+ProtoMinor
+Request.Method
+TransferEncoding
+Trailer
+Body
+ContentLength
+Header, values for non-canonical keys will have unpredictable behavior
+</pre>
+<p>
+The Response Body is closed after it is sent.
+</p>
+
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="ResponseWriter">type <a href="https://golang.org/src/net/http/server.go?s=1517:2599#L48">ResponseWriter</a></h2>
+                       <pre>type ResponseWriter interface {
+        <span class="comment">// Header returns the header map that will be sent by</span>
+        <span class="comment">// WriteHeader. Changing the header after a call to</span>
+        <span class="comment">// WriteHeader (or Write) has no effect unless the modified</span>
+        <span class="comment">// headers were declared as trailers by setting the</span>
+        <span class="comment">// "Trailer" header before the call to WriteHeader (see example).</span>
+        <span class="comment">// To suppress implicit response headers, set their value to nil.</span>
+        Header() <a href="https://golang.org/pkg/net/http/#Header">Header</a>
+
+        <span class="comment">// Write writes the data to the connection as part of an HTTP reply.</span>
+        <span class="comment">// If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK)</span>
+        <span class="comment">// before writing the data.  If the Header does not contain a</span>
+        <span class="comment">// Content-Type line, Write adds a Content-Type set to the result of passing</span>
+        <span class="comment">// the initial 512 bytes of written data to DetectContentType.</span>
+        Write([]<a href="https://golang.org/pkg/builtin/#byte">byte</a>) (<a href="https://golang.org/pkg/builtin/#int">int</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+
+        <span class="comment">// WriteHeader sends an HTTP response header with status code.</span>
+        <span class="comment">// If WriteHeader is not called explicitly, the first call to Write</span>
+        <span class="comment">// will trigger an implicit WriteHeader(http.StatusOK).</span>
+        <span class="comment">// Thus explicit calls to WriteHeader are mainly used to</span>
+        <span class="comment">// send error codes.</span>
+        WriteHeader(<a href="https://golang.org/pkg/builtin/#int">int</a>)
+}</pre>
+                       <p>
+A ResponseWriter interface is used by an HTTP handler to
+construct an HTTP response.
+</p>
+
+
+                       
+
+                       
+
+                       <div id="example_ResponseWriter_trailers" class="toggle">
+       <div class="collapsed">
+               <p class="exampleHeading toggleButton">▹ <span class="text">Example (Trailers)</span></p>
+       </div>
+       <div class="expanded">
+               <p class="exampleHeading toggleButton">▾ <span class="text">Example (Trailers)</span></p>
+               <p>HTTP Trailers are a set of key/value pairs like headers that come
+after the HTTP response, instead of before.
+</p>
+               
+               
+                       <div class="play">
+                               <div class="input"><textarea class="code">package main
+
+import (
+       "io"
+       "net/http"
+)
+
+func main() {
+       mux := http.NewServeMux()
+       mux.HandleFunc("/sendstrailers", func(w http.ResponseWriter, req *http.Request) {
+               // Before any call to WriteHeader or Write, declare
+               // the trailers you will set during the HTTP
+               // response. These three headers are actually sent in
+               // the trailer.
+               w.Header().Set("Trailer", "AtEnd1, AtEnd2")
+               w.Header().Add("Trailer", "AtEnd3")
+
+               w.Header().Set("Content-Type", "text/plain; charset=utf-8") // normal header
+               w.WriteHeader(http.StatusOK)
+
+               w.Header().Set("AtEnd1", "value 1")
+               io.WriteString(w, "This HTTP response has both headers before this text and trailers at the end.\n")
+               w.Header().Set("AtEnd2", "value 2")
+               w.Header().Set("AtEnd3", "value 3") // These will appear as trailers.
+       })
+}
+</textarea></div>
+                               <div class="output"><pre></pre></div>
+                               <div class="buttons">
+                                       <a class="run" title="Run this code [shift-enter]">Run</a>
+                                       <a class="fmt" title="Format this code">Format</a>
+                                       
+                               </div>
+                       </div>
+               
+       </div>
+</div>
+
+                       
+                       
+
+                       
+
+                       
+               
+                       
+                       
+                       <h2 id="RoundTripper">type <a href="https://golang.org/src/net/http/client.go?s=2750:3517#L73">RoundTripper</a></h2>
+                       <pre>type RoundTripper interface {
+        <span class="comment">// RoundTrip executes a single HTTP transaction, returning</span>
+        <span class="comment">// the Response for the request req.  RoundTrip should not</span>
+        <span class="comment">// attempt to interpret the response.  In particular,</span>
+        <span class="comment">// RoundTrip must return err == nil if it obtained a response,</span>
+        <span class="comment">// regardless of the response's HTTP status code.  A non-nil</span>
+        <span class="comment">// err should be reserved for failure to obtain a response.</span>
+        <span class="comment">// Similarly, RoundTrip should not attempt to handle</span>
+        <span class="comment">// higher-level protocol details such as redirects,</span>
+        <span class="comment">// authentication, or cookies.</span>
+        <span class="comment">//</span>
+        <span class="comment">// RoundTrip should not modify the request, except for</span>
+        <span class="comment">// consuming and closing the Body, including on errors. The</span>
+        <span class="comment">// request's URL and Header fields are guaranteed to be</span>
+        <span class="comment">// initialized.</span>
+        RoundTrip(*<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (*<a href="https://golang.org/pkg/net/http/#Response">Response</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+}</pre>
+                       <p>
+RoundTripper is an interface representing the ability to execute a
+single HTTP transaction, obtaining the Response for a given Request.
+</p>
+<p>
+A RoundTripper must be safe for concurrent use by multiple
+goroutines.
+</p>
+
+
+                       
+
+                       
+                               <pre>var <span id="DefaultTransport">DefaultTransport</span> <a href="https://golang.org/pkg/net/http/#RoundTripper">RoundTripper</a> = &amp;<a href="https://golang.org/pkg/net/http/#Transport">Transport</a>{
+        <a href="https://golang.org/pkg/net/http/#Proxy">Proxy</a>: <a href="https://golang.org/pkg/net/http/#ProxyFromEnvironment">ProxyFromEnvironment</a>,
+        <a href="https://golang.org/pkg/net/http/#Dial">Dial</a>: (&amp;<a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Dialer">Dialer</a>{
+                <a href="https://golang.org/pkg/net/http/#Timeout">Timeout</a>:   30 * <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Second">Second</a>,
+                <a href="https://golang.org/pkg/net/http/#KeepAlive">KeepAlive</a>: 30 * <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Second">Second</a>,
+        }).<a href="https://golang.org/pkg/net/http/#Dial">Dial</a>,
+        <a href="https://golang.org/pkg/net/http/#TLSHandshakeTimeout">TLSHandshakeTimeout</a>: 10 * <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Second">Second</a>,
+}</pre>
+                               <p>
+DefaultTransport is the default implementation of Transport and is
+used by DefaultClient. It establishes network connections as needed
+and caches them for reuse by subsequent calls. It uses HTTP proxies
+as directed by the $HTTP_PROXY and $NO_PROXY (or $http_proxy and
+$no_proxy) environment variables.
+</p>
+
+                       
+
+                       
+                       
+                       
+
+                       
+                               
+                               <h3 id="NewFileTransport">func <a href="https://golang.org/src/net/http/filetransport.go?s=827:876#L20">NewFileTransport</a></h3>
+                               <pre>func NewFileTransport(fs <a href="https://golang.org/pkg/net/http/#FileSystem">FileSystem</a>) <a href="https://golang.org/pkg/net/http/#RoundTripper">RoundTripper</a></pre>
+                               <p>
+NewFileTransport returns a new RoundTripper, serving the provided
+FileSystem. The returned RoundTripper ignores the URL host in its
+incoming requests, as well as most other properties of the
+request.
+</p>
+<p>
+The typical use case for NewFileTransport is to register the "file"
+protocol with a Transport, as in:
+</p>
+<pre>t := &amp;http.Transport{}
+t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
+c := &amp;http.Client{Transport: t}
+res, err := c.Get("file:///etc/passwd")
+...
+</pre>
+
+                               
+                               
+                       
+
+                       
+               
+                       
+                       
+                       <h2 id="ServeMux">type <a href="https://golang.org/src/net/http/server.go?s=46684:46809#L1569">ServeMux</a></h2>
+                       <pre>type ServeMux struct {
+        <span class="comment">// contains filtered or unexported fields</span>
+}</pre>
+                       <p>
+ServeMux is an HTTP request multiplexer.
+It matches the URL of each incoming request against a list of registered
+patterns and calls the handler for the pattern that
+most closely matches the URL.
+</p>
+<p>
+Patterns name fixed, rooted paths, like "/favicon.ico",
+or rooted subtrees, like "/images/" (note the trailing slash).
+Longer patterns take precedence over shorter ones, so that
+if there are handlers registered for both "/images/"
+and "/images/thumbnails/", the latter handler will be
+called for paths beginning "/images/thumbnails/" and the
+former will receive requests for any other paths in the
+"/images/" subtree.
+</p>
+<p>
+Note that since a pattern ending in a slash names a rooted subtree,
+the pattern "/" matches all paths not matched by other registered
+patterns, not just the URL with Path == "/".
+</p>
+<p>
+Patterns may optionally begin with a host name, restricting matches to
+URLs on that host only.  Host-specific patterns take precedence over
+general patterns, so that a handler might register for the two patterns
+"/codesearch" and "codesearch.google.com/" without also taking over
+requests for "<a href="http://www.google.com/">http://www.google.com/</a>".
+</p>
+<p>
+ServeMux also takes care of sanitizing the URL request path,
+redirecting any request containing . or .. elements to an
+equivalent .- and ..-free URL.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+                               
+                               <h3 id="NewServeMux">func <a href="https://golang.org/src/net/http/server.go?s=46940:46968#L1582">NewServeMux</a></h3>
+                               <pre>func NewServeMux() *<a href="https://golang.org/pkg/net/http/#ServeMux">ServeMux</a></pre>
+                               <p>
+NewServeMux allocates and returns a new ServeMux.
+</p>
+
+                               
+                               
+                       
+
+                       
+                               
+                               <h3 id="ServeMux.Handle">func (*ServeMux) <a href="https://golang.org/src/net/http/server.go?s=49985:50045#L1694">Handle</a></h3>
+                               <pre>func (mux *<a href="https://golang.org/pkg/net/http/#ServeMux">ServeMux</a>) Handle(pattern <a href="https://golang.org/pkg/builtin/#string">string</a>, handler <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>)</pre>
+                               <p>
+Handle registers the handler for the given pattern.
+If a handler already exists for pattern, Handle panics.
+</p>
+
+                               
+                               <div id="example_ServeMux_Handle" class="toggle">
+       <div class="collapsed">
+               <p class="exampleHeading toggleButton">▹ <span class="text">Example</span></p>
+       </div>
+       <div class="expanded">
+               <p class="exampleHeading toggleButton">▾ <span class="text">Example</span></p>
+               
+               
+               
+                       <p>Code:</p>
+                       <pre class="code">    mux := http.NewServeMux()
+    mux.Handle("/api/", apiHandler{})
+    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+            <span class="comment">// The "/" pattern matches everything, so we need to check</span>
+            <span class="comment">// that we're at the root here.</span>
+            if req.URL.Path != "/" {
+                    http.NotFound(w, req)
+                    return
+            }
+            fmt.Fprintf(w, "Welcome to the home page!")
+    })
+</pre>
+                       
+               
+       </div>
+</div>
+
+                               
+                       
+                               
+                               <h3 id="ServeMux.HandleFunc">func (*ServeMux) <a href="https://golang.org/src/net/http/server.go?s=51148:51235#L1733">HandleFunc</a></h3>
+                               <pre>func (mux *<a href="https://golang.org/pkg/net/http/#ServeMux">ServeMux</a>) HandleFunc(pattern <a href="https://golang.org/pkg/builtin/#string">string</a>, handler func(<a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, *<a href="https://golang.org/pkg/net/http/#Request">Request</a>))</pre>
+                               <p>
+HandleFunc registers the handler function for the given pattern.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="ServeMux.Handler">func (*ServeMux) <a href="https://golang.org/src/net/http/server.go?s=48689:48757#L1646">Handler</a></h3>
+                               <pre>func (mux *<a href="https://golang.org/pkg/net/http/#ServeMux">ServeMux</a>) Handler(r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (h <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>, pattern <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+                               <p>
+Handler returns the handler to use for the given request,
+consulting r.Method, r.Host, and r.URL.Path. It always returns
+a non-nil handler. If the path is not in its canonical form, the
+handler will be an internally-generated handler that redirects
+to the canonical path.
+</p>
+<p>
+Handler also returns the registered pattern that matches the
+request or, in the case of internally-generated redirects,
+the pattern that will match after following the redirect.
+</p>
+<p>
+If there is no registered handler that applies to the request,
+Handler returns a “page not found” handler and an empty pattern.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="ServeMux.ServeHTTP">func (*ServeMux) <a href="https://golang.org/src/net/http/server.go?s=49617:49677#L1680">ServeHTTP</a></h3>
+                               <pre>func (mux *<a href="https://golang.org/pkg/net/http/#ServeMux">ServeMux</a>) ServeHTTP(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)</pre>
+                               <p>
+ServeHTTP dispatches the request to the handler whose
+pattern most closely matches the request URL.
+</p>
+
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="Server">type <a href="https://golang.org/src/net/http/server.go?s=52296:53789#L1760">Server</a></h2>
+                       <pre>type Server struct {
+        Addr           <a href="https://golang.org/pkg/builtin/#string">string</a>        <span class="comment">// TCP address to listen on, ":http" if empty</span>
+        Handler        <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>       <span class="comment">// handler to invoke, http.DefaultServeMux if nil</span>
+        ReadTimeout    <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a> <span class="comment">// maximum duration before timing out read of the request</span>
+        WriteTimeout   <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a> <span class="comment">// maximum duration before timing out write of the response</span>
+        MaxHeaderBytes <a href="https://golang.org/pkg/builtin/#int">int</a>           <span class="comment">// maximum size of request headers, DefaultMaxHeaderBytes if 0</span>
+        TLSConfig      *<a href="https://golang.org/pkg/crypto/tls/">tls</a>.<a href="https://golang.org/pkg/crypto/tls/#Config">Config</a>   <span class="comment">// optional TLS config, used by ListenAndServeTLS</span>
+
+        <span class="comment">// TLSNextProto optionally specifies a function to take over</span>
+        <span class="comment">// ownership of the provided TLS connection when an NPN</span>
+        <span class="comment">// protocol upgrade has occurred.  The map key is the protocol</span>
+        <span class="comment">// name negotiated. The Handler argument should be used to</span>
+        <span class="comment">// handle HTTP requests and will initialize the Request's TLS</span>
+        <span class="comment">// and RemoteAddr if not already set.  The connection is</span>
+        <span class="comment">// automatically closed when the function returns.</span>
+        TLSNextProto map[<a href="https://golang.org/pkg/builtin/#string">string</a>]func(*<a href="https://golang.org/pkg/net/http/#Server">Server</a>, *<a href="https://golang.org/pkg/crypto/tls/">tls</a>.<a href="https://golang.org/pkg/crypto/tls/#Conn">Conn</a>, <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>)
+
+        <span class="comment">// ConnState specifies an optional callback function that is</span>
+        <span class="comment">// called when a client connection changes state. See the</span>
+        <span class="comment">// ConnState type and associated constants for details.</span>
+        ConnState func(<a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Conn">Conn</a>, <a href="https://golang.org/pkg/net/http/#ConnState">ConnState</a>)
+
+        <span class="comment">// ErrorLog specifies an optional logger for errors accepting</span>
+        <span class="comment">// connections and unexpected behavior from handlers.</span>
+        <span class="comment">// If nil, logging goes to os.Stderr via the log package's</span>
+        <span class="comment">// standard logger.</span>
+        ErrorLog *<a href="https://golang.org/pkg/log/">log</a>.<a href="https://golang.org/pkg/log/#Logger">Logger</a>
+        <span class="comment">// contains filtered or unexported fields</span>
+}</pre>
+                       <p>
+A Server defines parameters for running an HTTP server.
+The zero value for Server is a valid configuration.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+                               
+                               <h3 id="Server.ListenAndServe">func (*Server) <a href="https://golang.org/src/net/http/server.go?s=55886:55927#L1858">ListenAndServe</a></h3>
+                               <pre>func (srv *<a href="https://golang.org/pkg/net/http/#Server">Server</a>) ListenAndServe() <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                               <p>
+ListenAndServe listens on the TCP network address srv.Addr and then
+calls Serve to handle requests on incoming connections.  If
+srv.Addr is blank, ":http" is used.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Server.ListenAndServeTLS">func (*Server) <a href="https://golang.org/src/net/http/server.go?s=60146:60214#L2003">ListenAndServeTLS</a></h3>
+                               <pre>func (srv *<a href="https://golang.org/pkg/net/http/#Server">Server</a>) ListenAndServeTLS(certFile, keyFile <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                               <p>
+ListenAndServeTLS listens on the TCP network address srv.Addr and
+then calls Serve to handle requests on incoming TLS connections.
+</p>
+<p>
+Filenames containing a certificate and matching private key for the
+server must be provided if the Server's TLSConfig.Certificates is
+not populated. If the certificate is signed by a certificate
+authority, the certFile should be the concatenation of the server's
+certificate, any intermediates, and the CA's certificate.
+</p>
+<p>
+If srv.Addr is blank, ":https" is used.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Server.Serve">func (*Server) <a href="https://golang.org/src/net/http/server.go?s=56308:56354#L1873">Serve</a></h3>
+                               <pre>func (srv *<a href="https://golang.org/pkg/net/http/#Server">Server</a>) Serve(l <a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Listener">Listener</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+                               <p>
+Serve accepts incoming connections on the Listener l, creating a
+new service goroutine for each.  The service goroutines read requests and
+then call srv.Handler to reply to them.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Server.SetKeepAlivesEnabled">func (*Server) <a href="https://golang.org/src/net/http/server.go?s=57329:57376#L1912">SetKeepAlivesEnabled</a></h3>
+                               <pre>func (srv *<a href="https://golang.org/pkg/net/http/#Server">Server</a>) SetKeepAlivesEnabled(v <a href="https://golang.org/pkg/builtin/#bool">bool</a>)</pre>
+                               <p>
+SetKeepAlivesEnabled controls whether HTTP keep-alives are enabled.
+By default, keep-alives are always enabled. Only very
+resource-constrained environments or servers in the process of
+shutting down should disable them.
+</p>
+
+                               
+                               
+                               
+                       
+               
+                       
+                       
+                       <h2 id="Transport">type <a href="https://golang.org/src/net/http/transport.go?s=1324:4038#L39">Transport</a></h2>
+                       <pre>type Transport struct {
+
+        <span class="comment">// Proxy specifies a function to return a proxy for a given</span>
+        <span class="comment">// Request. If the function returns a non-nil error, the</span>
+        <span class="comment">// request is aborted with the provided error.</span>
+        <span class="comment">// If Proxy is nil or returns a nil *URL, no proxy is used.</span>
+        Proxy func(*<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (*<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+
+        <span class="comment">// Dial specifies the dial function for creating unencrypted</span>
+        <span class="comment">// TCP connections.</span>
+        <span class="comment">// If Dial is nil, net.Dial is used.</span>
+        Dial func(network, addr <a href="https://golang.org/pkg/builtin/#string">string</a>) (<a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Conn">Conn</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+
+        <span class="comment">// DialTLS specifies an optional dial function for creating</span>
+        <span class="comment">// TLS connections for non-proxied HTTPS requests.</span>
+        <span class="comment">//</span>
+        <span class="comment">// If DialTLS is nil, Dial and TLSClientConfig are used.</span>
+        <span class="comment">//</span>
+        <span class="comment">// If DialTLS is set, the Dial hook is not used for HTTPS</span>
+        <span class="comment">// requests and the TLSClientConfig and TLSHandshakeTimeout</span>
+        <span class="comment">// are ignored. The returned net.Conn is assumed to already be</span>
+        <span class="comment">// past the TLS handshake.</span>
+        DialTLS func(network, addr <a href="https://golang.org/pkg/builtin/#string">string</a>) (<a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Conn">Conn</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+
+        <span class="comment">// TLSClientConfig specifies the TLS configuration to use with</span>
+        <span class="comment">// tls.Client. If nil, the default configuration is used.</span>
+        TLSClientConfig *<a href="https://golang.org/pkg/crypto/tls/">tls</a>.<a href="https://golang.org/pkg/crypto/tls/#Config">Config</a>
+
+        <span class="comment">// TLSHandshakeTimeout specifies the maximum amount of time waiting to</span>
+        <span class="comment">// wait for a TLS handshake. Zero means no timeout.</span>
+        TLSHandshakeTimeout <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a>
+
+        <span class="comment">// DisableKeepAlives, if true, prevents re-use of TCP connections</span>
+        <span class="comment">// between different HTTP requests.</span>
+        DisableKeepAlives <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+
+        <span class="comment">// DisableCompression, if true, prevents the Transport from</span>
+        <span class="comment">// requesting compression with an "Accept-Encoding: gzip"</span>
+        <span class="comment">// request header when the Request contains no existing</span>
+        <span class="comment">// Accept-Encoding value. If the Transport requests gzip on</span>
+        <span class="comment">// its own and gets a gzipped response, it's transparently</span>
+        <span class="comment">// decoded in the Response.Body. However, if the user</span>
+        <span class="comment">// explicitly requested gzip it is not automatically</span>
+        <span class="comment">// uncompressed.</span>
+        DisableCompression <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+
+        <span class="comment">// MaxIdleConnsPerHost, if non-zero, controls the maximum idle</span>
+        <span class="comment">// (keep-alive) to keep per-host.  If zero,</span>
+        <span class="comment">// DefaultMaxIdleConnsPerHost is used.</span>
+        MaxIdleConnsPerHost <a href="https://golang.org/pkg/builtin/#int">int</a>
+
+        <span class="comment">// ResponseHeaderTimeout, if non-zero, specifies the amount of</span>
+        <span class="comment">// time to wait for a server's response headers after fully</span>
+        <span class="comment">// writing the request (including its body, if any). This</span>
+        <span class="comment">// time does not include the time to read the response body.</span>
+        ResponseHeaderTimeout <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a>
+        <span class="comment">// contains filtered or unexported fields</span>
+}</pre>
+                       <p>
+Transport is an implementation of RoundTripper that supports HTTP,
+HTTPS, and HTTP proxies (for either HTTP or HTTPS with CONNECT).
+Transport can also cache connections for future re-use.
+</p>
+
+
+                       
+
+                       
+
+                       
+                       
+                       
+
+                       
+
+                       
+                               
+                               <h3 id="Transport.CancelRequest">func (*Transport) <a href="https://golang.org/src/net/http/transport.go?s=8881:8928#L269">CancelRequest</a></h3>
+                               <pre>func (t *<a href="https://golang.org/pkg/net/http/#Transport">Transport</a>) CancelRequest(req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)</pre>
+                               <p>
+CancelRequest cancels an in-flight request by closing its connection.
+CancelRequest should only be called after RoundTrip has returned.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Transport.CloseIdleConnections">func (*Transport) <a href="https://golang.org/src/net/http/transport.go?s=8498:8540#L253">CloseIdleConnections</a></h3>
+                               <pre>func (t *<a href="https://golang.org/pkg/net/http/#Transport">Transport</a>) CloseIdleConnections()</pre>
+                               <p>
+CloseIdleConnections closes any connections which were previously
+connected from previous requests but are now sitting idle in
+a "keep-alive" state. It does not interrupt any connections currently
+in use.
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Transport.RegisterProtocol">func (*Transport) <a href="https://golang.org/src/net/http/transport.go?s=7866:7934#L234">RegisterProtocol</a></h3>
+                               <pre>func (t *<a href="https://golang.org/pkg/net/http/#Transport">Transport</a>) RegisterProtocol(scheme <a href="https://golang.org/pkg/builtin/#string">string</a>, rt <a href="https://golang.org/pkg/net/http/#RoundTripper">RoundTripper</a>)</pre>
+                               <p>
+RegisterProtocol registers a new protocol with scheme.
+The Transport will pass requests using the given scheme to rt.
+It is rt's responsibility to simulate HTTP request semantics.
+</p>
+<p>
+RegisterProtocol can be used by other packages to provide
+implementations of protocol schemes like "ftp" or "file".
+</p>
+
+                               
+                               
+                               
+                       
+                               
+                               <h3 id="Transport.RoundTrip">func (*Transport) <a href="https://golang.org/src/net/http/transport.go?s=6344:6415#L181">RoundTrip</a></h3>
+                               <pre>func (t *<a href="https://golang.org/pkg/net/http/#Transport">Transport</a>) RoundTrip(req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+                               <p>
+RoundTrip implements the RoundTripper interface.
+</p>
+<p>
+For higher-level HTTP client support (such as handling of cookies
+and redirects), see Get, Post, and the Client type.
+</p>
+
+                               
+                               
+                               
+                       
+               
+       
+
+       
+
+
+
+
+
+       
+       
+               <h2 id="pkg-subdirectories">Subdirectories</h2>
+       
+       
+
+
+       <div class="pkg-dir">
+               <table>
+                       <tbody><tr>
+                               <th class="pkg-name">Name</th>
+                               <th class="pkg-synopsis">Synopsis</th>
+                       </tr>
+
+                       
+                       <tr>
+                               <td colspan="2"><a href="https://golang.org/pkg/net/">..</a></td>
+                       </tr>
+                       
+
+                       
+                               
+                                       <tr>
+                                               <td class="pkg-name" style="padding-left: 0px;">
+                                                       <a href="https://golang.org/pkg/net/http/cgi/">cgi</a>
+                                               </td>
+                                               <td class="pkg-synopsis">
+                                                       Package cgi implements CGI (Common Gateway Interface) as specified in RFC 3875.
+                                               </td>
+                                       </tr>
+                               
+                       
+                               
+                                       <tr>
+                                               <td class="pkg-name" style="padding-left: 0px;">
+                                                       <a href="https://golang.org/pkg/net/http/cookiejar/">cookiejar</a>
+                                               </td>
+                                               <td class="pkg-synopsis">
+                                                       Package cookiejar implements an in-memory RFC 6265-compliant http.CookieJar.
+                                               </td>
+                                       </tr>
+                               
+                       
+                               
+                                       <tr>
+                                               <td class="pkg-name" style="padding-left: 0px;">
+                                                       <a href="https://golang.org/pkg/net/http/fcgi/">fcgi</a>
+                                               </td>
+                                               <td class="pkg-synopsis">
+                                                       Package fcgi implements the FastCGI protocol.
+                                               </td>
+                                       </tr>
+                               
+                       
+                               
+                                       <tr>
+                                               <td class="pkg-name" style="padding-left: 0px;">
+                                                       <a href="https://golang.org/pkg/net/http/httptest/">httptest</a>
+                                               </td>
+                                               <td class="pkg-synopsis">
+                                                       Package httptest provides utilities for HTTP testing.
+                                               </td>
+                                       </tr>
+                               
+                       
+                               
+                                       <tr>
+                                               <td class="pkg-name" style="padding-left: 0px;">
+                                                       <a href="https://golang.org/pkg/net/http/httputil/">httputil</a>
+                                               </td>
+                                               <td class="pkg-synopsis">
+                                                       Package httputil provides HTTP utility functions, complementing the more common ones in the net/http package.
+                                               </td>
+                                       </tr>
+                               
+                       
+                               
+                                       <tr>
+                                               <td class="pkg-name" style="padding-left: 0px;">
+                                                       <a href="https://golang.org/pkg/net/http/pprof/">pprof</a>
+                                               </td>
+                                               <td class="pkg-synopsis">
+                                                       Package pprof serves via its HTTP server runtime profiling data in the format expected by the pprof visualization tool.
+                                               </td>
+                                       </tr>
+                               
+                       
+               </tbody></table>
+       </div>
+
+
+       
+
+
+
+<div id="footer">
+Build version go1.5.1.<br>
+Except as <a href="https://developers.google.com/site-policies#restrictions">noted</a>,
+the content of this page is licensed under the
+Creative Commons Attribution 3.0 License,
+and code is licensed under a <a href="https://golang.org/LICENSE">BSD license</a>.<br>
+<a href="https://golang.org/doc/tos.html">Terms of Service</a> | 
+<a href="http://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
+</div>
+
+</div><!-- .container -->
+</div><!-- #page -->
+
+<!-- TODO(adonovan): load these from <head> using "defer" attribute? -->
+<script type="text/javascript" src="./http - The Go Programming Language_files/jquery.min.js"></script>
+<script type="text/javascript" src="./http - The Go Programming Language_files/jquery.treeview.js"></script>
+<script type="text/javascript" src="./http - The Go Programming Language_files/jquery.treeview.edit.js"></script>
+
+
+<script type="text/javascript" src="./http - The Go Programming Language_files/playground.js"></script>
+
+<script type="text/javascript" src="./http - The Go Programming Language_files/godocs.js"></script>
+
+<script type="text/javascript">
+(function() {
+  var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
+  ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
+  var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
+})();
+</script>
+
+
+
+</body></html>
\ No newline at end of file
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/append_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/append_object.go
new file mode 100644 (file)
index 0000000..42212f4
--- /dev/null
@@ -0,0 +1,164 @@
+// Package sample examples
+package sample
+
+import (
+       "bytes"
+       "fmt"
+       "io/ioutil"
+       "os"
+       "strconv"
+       "strings"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// AppendObjectSample shows the append file's usage
+func AppendObjectSample() {
+       // Create bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = bucket.DeleteObject(objectKey)
+
+       var str = "弃我去者,昨日之日不可留。 乱我心者,今日之日多烦忧!"
+       var nextPos int64
+
+       // Case 1: Append a string to the object
+       // The first append position is 0 and the return value is for the next append's position.
+       nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Second append
+       nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Download
+       body, err := bucket.GetObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       data, err := ioutil.ReadAll(body)
+       body.Close()
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println(objectKey, ":", string(data))
+
+       err = bucket.DeleteObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2: Append byte array to the object
+       nextPos = 0
+       // The first append position is 0, and the return value is for the next append's position.
+       nextPos, err = bucket.AppendObject(objectKey, bytes.NewReader([]byte(str)), nextPos)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Second append
+       nextPos, err = bucket.AppendObject(objectKey, bytes.NewReader([]byte(str)), nextPos)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Download
+       body, err = bucket.GetObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       data, err = ioutil.ReadAll(body)
+       body.Close()
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println(objectKey, ":", string(data))
+
+       err = bucket.DeleteObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 3: Append a local file to the object
+       fd, err := os.Open(localFile)
+       if err != nil {
+               HandleError(err)
+       }
+       defer fd.Close()
+
+       nextPos = 0
+       nextPos, err = bucket.AppendObject(objectKey, fd, nextPos)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 4: Get the next append position by GetObjectDetailedMeta
+       props, err := bucket.GetObjectDetailedMeta(objectKey)
+       nextPos, err = strconv.ParseInt(props.Get(oss.HTTPHeaderOssNextAppendPosition), 10, 64)
+       if err != nil {
+               HandleError(err)
+       }
+
+       nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos)
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = bucket.DeleteObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 5: Specify the object properties for the first append, including the "x-oss-meta"'s custom metadata.
+       options := []oss.Option{
+               oss.Expires(futureDate),
+               oss.ObjectACL(oss.ACLPublicRead),
+               oss.Meta("myprop", "mypropval")}
+       nextPos = 0
+       fd.Seek(0, os.SEEK_SET)
+       nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos, options...)
+       if err != nil {
+               HandleError(err)
+       }
+       // Second append
+       fd.Seek(0, os.SEEK_SET)
+       nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos)
+       if err != nil {
+               HandleError(err)
+       }
+
+       props, err = bucket.GetObjectDetailedMeta(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("myprop:", props.Get("x-oss-meta-myprop"))
+
+       goar, err := bucket.GetObjectACL(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Object ACL:", goar.ACL)
+
+       // Case 6: Set the storage classes.OSS provides three storage classes: Standard, Infrequent Access, and Archive.
+       // Upload a strings, and you can append some strings in the behind of object. but the object is 'Archive' storange class.
+       // An object created with the AppendObject operation is an appendable object. set the object storange-class to 'Archive'.
+       nextPos, err = bucket.AppendObject(appendObjectKey, strings.NewReader("昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,"), nextPos, oss.ObjectStorageClass("Archive"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete the object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("AppendObjectSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/archive.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/archive.go
new file mode 100644 (file)
index 0000000..1002ef5
--- /dev/null
@@ -0,0 +1,74 @@
+package sample
+
+import (
+       "fmt"
+       "strings"
+       "time"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// ArchiveSample archives sample
+func ArchiveSample() {
+       // Create archive bucket
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = client.CreateBucket(bucketName, oss.StorageClass(oss.StorageArchive))
+       if err != nil {
+               HandleError(err)
+       }
+
+       archiveBucket, err := client.Bucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Put archive object
+       var val = "花间一壶酒,独酌无相亲。 举杯邀明月,对影成三人。"
+       err = archiveBucket.PutObject(objectKey, strings.NewReader(val))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Check whether the object is archive class
+       meta, err := archiveBucket.GetObjectDetailedMeta(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       if meta.Get("X-Oss-Storage-Class") == string(oss.StorageArchive) {
+               // Restore object
+               err = archiveBucket.RestoreObject(objectKey)
+               if err != nil {
+                       HandleError(err)
+               }
+
+               // Wait for restore completed
+               meta, err = archiveBucket.GetObjectDetailedMeta(objectKey)
+               for meta.Get("X-Oss-Restore") == "ongoing-request=\"true\"" {
+                       fmt.Println("x-oss-restore:" + meta.Get("X-Oss-Restore"))
+                       time.Sleep(1000 * time.Second)
+                       meta, err = archiveBucket.GetObjectDetailedMeta(objectKey)
+               }
+       }
+
+       // Get restored object
+       err = archiveBucket.GetObjectToFile(objectKey, localFile)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Restore repeatedly
+       err = archiveBucket.RestoreObject(objectKey)
+
+       // Delete object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("ArchiveSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_acl.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_acl.go
new file mode 100644 (file)
index 0000000..92c78c7
--- /dev/null
@@ -0,0 +1,43 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketACLSample shows how to get and set the bucket ACL
+func BucketACLSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create a bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Set bucket ACL. The valid ACLs are ACLPrivate、ACLPublicRead、ACLPublicReadWrite
+       err = client.SetBucketACL(bucketName, oss.ACLPublicRead)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get bucket ACL
+       gbar, err := client.GetBucketACL(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket ACL:", gbar.ACL)
+
+       // Delete the bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketACLSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_cors.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_cors.go
new file mode 100644 (file)
index 0000000..7af95e6
--- /dev/null
@@ -0,0 +1,71 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketCORSSample shows how to get or set the bucket CORS.
+func BucketCORSSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create the bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       rule1 := oss.CORSRule{
+               AllowedOrigin: []string{"*"},
+               AllowedMethod: []string{"PUT", "GET", "POST"},
+               AllowedHeader: []string{},
+               ExposeHeader:  []string{},
+               MaxAgeSeconds: 100,
+       }
+
+       rule2 := oss.CORSRule{
+               AllowedOrigin: []string{"http://www.a.com", "http://www.b.com"},
+               AllowedMethod: []string{"GET"},
+               AllowedHeader: []string{"Authorization"},
+               ExposeHeader:  []string{"x-oss-test", "x-oss-test1"},
+               MaxAgeSeconds: 100,
+       }
+
+       // Case 1: Set the bucket CORS rules
+       err = client.SetBucketCORS(bucketName, []oss.CORSRule{rule1})
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2: Set the bucket CORS rules. if CORS rules exist, they will be overwritten.
+       err = client.SetBucketCORS(bucketName, []oss.CORSRule{rule1, rule2})
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get the bucket's CORS
+       gbl, err := client.GetBucketCORS(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket CORS:", gbl.CORSRules)
+
+       // Delete bucket's CORS
+       err = client.DeleteBucketCORS(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketCORSSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_encryption.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_encryption.go
new file mode 100644 (file)
index 0000000..38c0ec9
--- /dev/null
@@ -0,0 +1,51 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketEncryptionSample shows how to get and set the bucket encryption Algorithm
+func BucketEncryptionSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create a bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // SetBucketEncryption:AES256 ,"123"
+       encryptionRule := oss.ServerEncryptionRule{}
+       encryptionRule.SSEDefault.SSEAlgorithm = string(oss.AESAlgorithm)
+       err = client.SetBucketEncryption(bucketName, encryptionRule)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get bucket encryption
+       encryptionResult, err := client.GetBucketEncryption(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket Encryption:", encryptionResult)
+
+       // Delete the bucket
+       err = client.DeleteBucketEncryption(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete the object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketEncryptionSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_inventory.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_inventory.go
new file mode 100644 (file)
index 0000000..1621855
--- /dev/null
@@ -0,0 +1,102 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketInventorySample shows how to set, get, list and delete the bucket inventory configuration
+func BucketInventorySample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create the bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // the inventory configuration,not use any encryption
+       bl := true
+       invConfig := oss.InventoryConfiguration{
+               Id:        "report1",
+               IsEnabled: &bl,
+               Prefix:    "filterPrefix/",
+               OSSBucketDestination: oss.OSSBucketDestination{
+                       Format:    "CSV",
+                       AccountId: accountID,
+                       RoleArn:   stsARN,
+                       Bucket:    "acs:oss:::" + bucketName,
+                       Prefix:    "prefix1",
+               },
+               Frequency:              "Daily",
+               IncludedObjectVersions: "All",
+               OptionalFields: oss.OptionalFields{
+                       Field: []string{
+                               "Size", "LastModifiedDate", "ETag", "StorageClass", "IsMultipartUploaded", "EncryptionStatus",
+                       },
+               },
+       }
+
+       // case 1: Set inventory
+       err = client.SetBucketInventory(bucketName, invConfig)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // case 2: Get Bucket inventory
+       ret, err := client.GetBucketInventory(bucketName, invConfig.Id)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket inventory:", ret)
+
+       // case 3: List Bucket inventory
+       invConfig2 := oss.InventoryConfiguration{
+               Id:        "report2",
+               IsEnabled: &bl,
+               Prefix:    "filterPrefix/",
+               OSSBucketDestination: oss.OSSBucketDestination{
+                       Format:    "CSV",
+                       AccountId: accountID,
+                       RoleArn:   stsARN,
+                       Bucket:    "acs:oss:::" + bucketName,
+                       Prefix:    "prefix1",
+               },
+               Frequency:              "Daily",
+               IncludedObjectVersions: "All",
+               OptionalFields: oss.OptionalFields{
+                       Field: []string{
+                               "Size", "LastModifiedDate", "ETag", "StorageClass", "IsMultipartUploaded", "EncryptionStatus",
+                       },
+               },
+       }
+       err = client.SetBucketInventory(bucketName, invConfig2)
+       if err != nil {
+               HandleError(err)
+       }
+       NextContinuationToken := ""
+       listInvConf, err := client.ListBucketInventory(bucketName, NextContinuationToken)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket inventory list:", listInvConf)
+
+       // case 4: Delete Bucket inventory
+       err = client.DeleteBucketInventory(bucketName, invConfig.Id)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketInventorySample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_lifecycle.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_lifecycle.go
new file mode 100644 (file)
index 0000000..29e83ca
--- /dev/null
@@ -0,0 +1,121 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketLifecycleSample shows how to set, get and delete bucket's lifecycle.
+func BucketLifecycleSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create the bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1: Set the lifecycle. The rule ID is rule1 and the applied objects' prefix is one and the last modified Date is before 2015/11/11
+       expriation := oss.LifecycleExpiration{
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+       }
+       rule1 := oss.LifecycleRule{
+               ID:         "rule1",
+               Prefix:     "one",
+               Status:     "Enabled",
+               Expiration: &expriation,
+       }
+       var rules = []oss.LifecycleRule{rule1}
+       err = client.SetBucketLifecycle(bucketName, rules)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2: Get the bucket's lifecycle
+       lc, err := client.GetBucketLifecycle(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Printf("Bucket Lifecycle:%v, %v\n", lc.Rules, *lc.Rules[0].Expiration)
+
+       // Case 3: Set the lifecycle, The rule ID is rule2 and the applied objects' prefix is two. The object start with the prefix will be transited to IA storage Type 3 days latter, and to archive storage type 30 days latter
+       transitionIA := oss.LifecycleTransition{
+               Days:         3,
+               StorageClass: oss.StorageIA,
+       }
+       transitionArch := oss.LifecycleTransition{
+               Days:         30,
+               StorageClass: oss.StorageArchive,
+       }
+       rule2 := oss.LifecycleRule{
+               ID:          "rule2",
+               Prefix:      "two",
+               Status:      "Enabled",
+               Transitions: []oss.LifecycleTransition{transitionIA, transitionArch},
+       }
+       rules = []oss.LifecycleRule{rule2}
+       err = client.SetBucketLifecycle(bucketName, rules)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 4: Set the lifecycle, The rule ID is rule3 and the applied objects' prefix is three. The object start with the prefix will be transited to IA storage Type 3 days latter, and to archive storage type 30 days latter, the uncompleted multipart upload will be abort 3 days latter.
+       abortMPU := oss.LifecycleAbortMultipartUpload{
+               Days: 3,
+       }
+       rule3 := oss.LifecycleRule{
+               ID:                   "rule3",
+               Prefix:               "three",
+               Status:               "Enabled",
+               AbortMultipartUpload: &abortMPU,
+       }
+       rules = append(lc.Rules, rule3)
+       err = client.SetBucketLifecycle(bucketName, rules)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 5: Set the lifecycle. The rule ID is rule4 and the applied objects' has the tagging which prefix is four and the last modified Date is before 2015/11/11
+       expriation = oss.LifecycleExpiration{
+               CreatedBeforeDate: "2015-11-11T00:00:00.000Z",
+       }
+       tag1 := oss.Tag{
+               Key:   "key1",
+               Value: "value1",
+       }
+       tag2 := oss.Tag{
+               Key:   "key2",
+               Value: "value2",
+       }
+       rule4 := oss.LifecycleRule{
+               ID:         "rule4",
+               Prefix:     "four",
+               Status:     "Enabled",
+               Tags:       []oss.Tag{tag1, tag2},
+               Expiration: &expriation,
+       }
+       rules = []oss.LifecycleRule{rule4}
+       err = client.SetBucketLifecycle(bucketName, rules)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 6: Delete bucket's Lifecycle
+       err = client.DeleteBucketLifecycle(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketLifecycleSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_logging.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_logging.go
new file mode 100644 (file)
index 0000000..3a7ad1b
--- /dev/null
@@ -0,0 +1,90 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketLoggingSample shows how to set, get and delete the bucket logging configuration
+func BucketLoggingSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create the bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       // Create target bucket to store the logging files.
+       var targetBucketName = "target-bucket"
+       err = client.CreateBucket(targetBucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1: Set the logging for the object prefixed with "prefix-1" and save their access logs to the target bucket
+       err = client.SetBucketLogging(bucketName, targetBucketName, "prefix-1", true)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2: Set the logging for the object prefixed with "prefix-2" and save their logs to the same bucket
+       // Note: the rule will overwrite other rules if they have same bucket and prefix
+       err = client.SetBucketLogging(bucketName, bucketName, "prefix-2", true)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete the bucket's logging configuration
+       err = client.DeleteBucketLogging(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 3: Set the logging without enabling it
+       err = client.SetBucketLogging(bucketName, targetBucketName, "prefix-3", false)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get the bucket's logging configuration
+       gbl, err := client.GetBucketLogging(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket Logging:", gbl.LoggingEnabled)
+
+       err = client.SetBucketLogging(bucketName, bucketName, "prefix2", true)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get the bucket's logging configuration
+       gbl, err = client.GetBucketLogging(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket Logging:", gbl.LoggingEnabled)
+
+       // Delete the bucket's logging configuration
+       err = client.DeleteBucketLogging(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       err = client.DeleteBucket(targetBucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketLoggingSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_policy.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_policy.go
new file mode 100644 (file)
index 0000000..54349bb
--- /dev/null
@@ -0,0 +1,67 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketPolicySample shows how to set, get and delete the bucket policy configuration
+func BucketPolicySample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create the bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // the policy string
+       var policyInfo string
+       policyInfo = `
+       {
+               "Version":"1",
+               "Statement":[
+                       {
+                               "Action":[
+                                       "oss:GetObject",
+                                       "oss:PutObject"
+                               ],
+                               "Effect":"Deny",
+                               "Principal":"[123456790]",
+                               "Resource":["acs:oss:*:1234567890:*/*"]
+                       }
+               ]
+       }`
+
+       // Set policy
+       err = client.SetBucketPolicy(bucketName, policyInfo)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get Bucket policy
+       ret, err := client.GetBucketPolicy(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket policy:", ret)
+
+       // Delete Bucket policy
+       err = client.DeleteBucketPolicy(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketPolicySample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_qosInfo.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_qosInfo.go
new file mode 100644 (file)
index 0000000..16a6df2
--- /dev/null
@@ -0,0 +1,65 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketQoSInfoSample shows how to set, get and delete the bucket QoS configuration
+func BucketQoSInfoSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create the bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       // Initial QoS Configuration
+       five := 5
+       four := 4
+       totalQps := 200
+       qosConf := oss.BucketQoSConfiguration{
+               TotalUploadBandwidth:      &five,
+               IntranetUploadBandwidth:   &four,
+               ExtranetUploadBandwidth:   &four,
+               TotalDownloadBandwidth:    &four,
+               IntranetDownloadBandwidth: &four,
+               ExtranetDownloadBandwidth: &four,
+               TotalQPS:                  &totalQps,
+               IntranetQPS:               &totalQps,
+               ExtranetQPS:               &totalQps,
+       }
+
+       // Set Qos Info
+       err = client.SetBucketQoSInfo(bucketName, qosConf)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get Qos Info
+       ret, err := client.GetBucketQosInfo(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Printf("Bucket QoSInfo\n  TotalUploadBandwidth: %d\n  IntranetUploadBandwidth: %d\n  ExtranetUploadBandwidth: %d\n",
+               *ret.TotalUploadBandwidth, *ret.IntranetUploadBandwidth, *ret.ExtranetUploadBandwidth)
+
+       // Delete QosInfo
+       err = client.DeleteBucketQosInfo(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketPolicySample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_referer.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_referer.go
new file mode 100644 (file)
index 0000000..7d1e4db
--- /dev/null
@@ -0,0 +1,57 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketRefererSample shows how to set, get and delete the bucket referer.
+func BucketRefererSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create the bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       var referers = []string{
+               "http://www.aliyun.com",
+               "http://www.???.aliyuncs.com",
+               "http://www.*.com",
+       }
+
+       // Case 1: Set referers. The referers are with wildcards ? and * which could represent one and zero to multiple characters
+       err = client.SetBucketReferer(bucketName, referers, false)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2: Clear referers
+       referers = []string{}
+       err = client.SetBucketReferer(bucketName, referers, true)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get bucket referer configuration
+       gbr, err := client.GetBucketReferer(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket Referers:", gbr.RefererList,
+               "AllowEmptyReferer:", gbr.AllowEmptyReferer)
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketRefererSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_requestpayment.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_requestpayment.go
new file mode 100644 (file)
index 0000000..c7e045f
--- /dev/null
@@ -0,0 +1,117 @@
+package sample
+
+import (
+       "fmt"
+       "strings"
+       "io/ioutil"
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketrRequestPaymentSample shows how to set, get the bucket request payment.
+func BucketrRequestPaymentSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create the bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       reqPayConf := oss.RequestPaymentConfiguration{
+               Payer: string(oss.Requester),
+       }
+
+       // Case 1: Set bucket request payment.
+       err = client.SetBucketRequestPayment(bucketName, reqPayConf)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get bucket request payment configuration
+       ret, err := client.GetBucketRequestPayment(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket request payer:", ret.Payer)
+
+       if credentialUID == "" {
+               fmt.Println("Please enter a credential User ID, if you want to test credential user.")
+               clearData(client, bucketName)
+               return
+       }
+       // Credential other User
+       policyInfo := `
+       {
+               "Version":"1",
+               "Statement":[
+                       {
+                               "Action":[
+                                       "oss:*"
+                               ],
+                               "Effect":"Allow",
+                               "Principal":["` + credentialUID + `"],
+                               "Resource":["acs:oss:*:*:` + bucketName + `", "acs:oss:*:*:` + bucketName + `/*"]
+                       }
+               ]
+       }`
+
+       err = client.SetBucketPolicy(bucketName, policyInfo)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // New a Credential client
+       creClient, err := oss.New(endpoint, credentialAccessID, credentialAccessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get credential bucket
+       creBucket, err := creClient.Bucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Put object by credential User
+       key := "testCredentialObject"
+       objectValue := "this is a test string."
+       // Put object
+       err = creBucket.PutObject(key, strings.NewReader(objectValue), oss.RequestPayer(oss.Requester))
+       if err != nil {
+               HandleError(err)
+       }
+       // Get object
+       body, err := creBucket.GetObject(key, oss.RequestPayer(oss.Requester))
+       if err != nil {
+               HandleError(err)
+       }
+       defer body.Close()
+
+       data, err := ioutil.ReadAll(body)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println(string(data))
+       
+       // Delete object
+       err = creBucket.DeleteObject(key, oss.RequestPayer(oss.Requester))
+       if err != nil {
+               HandleError(err)
+       }
+
+       clearData(client, bucketName)
+}
+
+func clearData(client *oss.Client, bucketName string) {
+       // Delete bucket
+       err := client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketrRequestPaymentSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_website.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_website.go
new file mode 100644 (file)
index 0000000..e58801e
--- /dev/null
@@ -0,0 +1,104 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketWebsiteSample shows how to set, get and delete the bucket website.
+func BucketWebsiteSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create the bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       //Define bucket website indexWebsite or errorWebsite
+       var indexWebsite = "myindex.html"
+       var errorWebsite = "myerror.html"
+
+       // Set bucket website indexWebsite or errorWebsite
+       err = client.SetBucketWebsite(bucketName, indexWebsite, errorWebsite)
+       if err != nil {
+               HandleError(err)
+       }
+
+       bEnable := true
+       bDisable := false
+
+       // Define one website detail
+       ruleOk := oss.RoutingRule{
+               RuleNumber: 1,
+               Condition: oss.Condition{
+                       KeyPrefixEquals:             "abc",
+                       HTTPErrorCodeReturnedEquals: 404,
+                       IncludeHeader: []oss.IncludeHeader{
+                               oss.IncludeHeader{
+                                       Key:    "host",
+                                       Equals: "test.oss-cn-beijing-internal.aliyuncs.com",
+                               },
+                       },
+               },
+               Redirect: oss.Redirect{
+                       RedirectType:          "Mirror",
+                       PassQueryString:       &bDisable,
+                       MirrorURL:             "http://www.test.com/",
+                       MirrorPassQueryString: &bEnable,
+                       MirrorFollowRedirect:  &bEnable,
+                       MirrorCheckMd5:        &bDisable,
+                       MirrorHeaders: oss.MirrorHeaders{
+                               PassAll: &bEnable,
+                               Pass:    []string{"key1", "key2"},
+                               Remove:  []string{"remove1", "remove2"},
+                               Set: []oss.MirrorHeaderSet{
+                                       oss.MirrorHeaderSet{
+                                               Key:   "setKey1",
+                                               Value: "setValue1",
+                                       },
+                               },
+                       },
+               },
+       }
+       wxmlDetail := oss.WebsiteXML{}
+       wxmlDetail.RoutingRules = append(wxmlDetail.RoutingRules, ruleOk)
+
+       // Get website
+       res, err := client.GetBucketWebsite(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Website IndexDocument:", res.IndexDocument.Suffix)
+
+       // Set bucket website detail
+       err = client.SetBucketWebsiteDetail(bucketName, wxmlDetail)
+       if err != nil {
+               HandleError(err)
+       }
+       // Get website Detail
+       res, err = client.GetBucketWebsite(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Website Redirect type:", res.RoutingRules[0].Redirect.RedirectType)
+
+       // Delete Website
+       err = client.DeleteBucketWebsite(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("BucketWebsiteSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/cname_sample.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/cname_sample.go
new file mode 100644 (file)
index 0000000..5e0b503
--- /dev/null
@@ -0,0 +1,95 @@
+package sample
+
+import (
+       "fmt"
+       "io/ioutil"
+       "strings"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// CnameSample shows the cname usage
+func CnameSample() {
+       // New client
+       client, err := oss.New(endpoint4Cname, accessID, accessKey, oss.UseCname(true))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create bucket
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Set bucket ACL
+       err = client.SetBucketACL(bucketName, oss.ACLPrivate)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Look up bucket ACL
+       gbar, err := client.GetBucketACL(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Bucket ACL:", gbar.ACL)
+
+       // List buckets, the list operation could not be done by cname's endpoint
+       _, err = client.ListBuckets()
+       if err == nil {
+               HandleError(err)
+       }
+
+       bucket, err := client.Bucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       objectValue := "长忆观潮, 满郭人争江上望。来疑沧海尽成空, 万面鼓声中。弄潮儿向涛头立, 手把红旗旗不湿。别来几向梦中看, 梦觉尚心寒。"
+
+       // Put object
+       err = bucket.PutObject(objectKey, strings.NewReader(objectValue))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get object
+       body, err := bucket.GetObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       data, err := ioutil.ReadAll(body)
+       body.Close()
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println(objectKey, ":", string(data))
+
+       // Put object from file
+       err = bucket.PutObjectFromFile(objectKey, localFile)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get object to file
+       err = bucket.GetObjectToFile(objectKey, localFile)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // List objects
+       lor, err := bucket.ListObjects()
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("objects:", lor.Objects)
+
+       // Delete object
+       err = bucket.DeleteObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("CnameSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/comm.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/comm.go
new file mode 100644 (file)
index 0000000..91ba23c
--- /dev/null
@@ -0,0 +1,181 @@
+package sample
+
+import (
+       "fmt"
+       "os"
+       "strings"
+       "time"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+var (
+       pastDate   = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
+       futureDate = time.Date(2049, time.January, 10, 23, 0, 0, 0, time.UTC)
+)
+
+// HandleError is the error handling method in the sample code
+func HandleError(err error) {
+       fmt.Println("occurred error:", err)
+       os.Exit(-1)
+}
+
+// GetTestBucket creates the test bucket
+func GetTestBucket(bucketName string) (*oss.Bucket, error) {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               return nil, err
+       }
+
+       // Create bucket
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               return nil, err
+       }
+
+       // Get bucket
+       bucket, err := client.Bucket(bucketName)
+       if err != nil {
+               return nil, err
+       }
+
+       return bucket, nil
+}
+
+// DeleteTestBucketAndLiveChannel 删除sample的channelname和bucket,该函数为了简化sample,让sample代码更明了
+func DeleteTestBucketAndLiveChannel(bucketName string) error {
+       // New Client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               return err
+       }
+
+       // Get Bucket
+       bucket, err := client.Bucket(bucketName)
+       if err != nil {
+               return err
+       }
+
+       marker := ""
+       for {
+               result, err := bucket.ListLiveChannel(oss.Marker(marker))
+               if err != nil {
+                       HandleError(err)
+               }
+
+               for _, channel := range result.LiveChannel {
+                       err := bucket.DeleteLiveChannel(channel.Name)
+                       if err != nil {
+                               HandleError(err)
+                       }
+               }
+
+               if result.IsTruncated {
+                       marker = result.NextMarker
+               } else {
+                       break
+               }
+       }
+
+       // Delete Bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// DeleteTestBucketAndObject deletes the test bucket and its objects
+func DeleteTestBucketAndObject(bucketName string) error {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               return err
+       }
+
+       // Get bucket
+       bucket, err := client.Bucket(bucketName)
+       if err != nil {
+               return err
+       }
+
+       // Delete part
+       keyMarker := oss.KeyMarker("")
+       uploadIDMarker := oss.UploadIDMarker("")
+       for {
+               lmur, err := bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
+               if err != nil {
+                       return err
+               }
+               for _, upload := range lmur.Uploads {
+                       var imur = oss.InitiateMultipartUploadResult{Bucket: bucket.BucketName,
+                               Key: upload.Key, UploadID: upload.UploadID}
+                       err = bucket.AbortMultipartUpload(imur)
+                       if err != nil {
+                               return err
+                       }
+               }
+               keyMarker = oss.KeyMarker(lmur.NextKeyMarker)
+               uploadIDMarker = oss.UploadIDMarker(lmur.NextUploadIDMarker)
+               if !lmur.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete objects
+       marker := oss.Marker("")
+       for {
+               lor, err := bucket.ListObjects(marker)
+               if err != nil {
+                       return err
+               }
+               for _, object := range lor.Objects {
+                       err = bucket.DeleteObject(object.Key)
+                       if err != nil {
+                               return err
+                       }
+               }
+               marker = oss.Marker(lor.NextMarker)
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// Object defines pair of key and value
+type Object struct {
+       Key   string
+       Value string
+}
+
+// CreateObjects creates some objects
+func CreateObjects(bucket *oss.Bucket, objects []Object) error {
+       for _, object := range objects {
+               err := bucket.PutObject(object.Key, strings.NewReader(object.Value))
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// DeleteObjects deletes some objects.
+func DeleteObjects(bucket *oss.Bucket, objects []Object) error {
+       for _, object := range objects {
+               err := bucket.DeleteObject(object.Key)
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/config.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/config.go
new file mode 100644 (file)
index 0000000..2e146e1
--- /dev/null
@@ -0,0 +1,36 @@
+package sample
+
+import "os"
+
+var (
+       // Sample code's env configuration. You need to specify them with the actual configuration if you want to run sample code
+       endpoint   = os.Getenv("OSS_TEST_ENDPOINT")
+       accessID   = os.Getenv("OSS_TEST_ACCESS_KEY_ID")
+       accessKey  = os.Getenv("OSS_TEST_ACCESS_KEY_SECRET")
+       bucketName = os.Getenv("OSS_TEST_BUCKET")
+       kmsID      = os.Getenv("OSS_TEST_KMS_ID")
+       accountID  = os.Getenv("OSS_TEST_ACCOUNT_ID")
+       stsARN     = os.Getenv("OSS_TEST_STS_ARN")
+
+       // Credential
+       credentialAccessID  = os.Getenv("OSS_CREDENTIAL_KEY_ID")
+       credentialAccessKey = os.Getenv("OSS_CREDENTIAL_KEY_SECRET")
+       credentialUID       = os.Getenv("OSS_CREDENTIAL_UID")
+
+       // The cname endpoint
+       endpoint4Cname = os.Getenv("OSS_TEST_CNAME_ENDPOINT")
+)
+
+const (
+
+       // The object name in the sample code
+       objectKey       string = "my-object"
+       appendObjectKey string = "my-object-append"
+
+       // The local files to run sample code.
+       localFile          string = "sample/BingWallpaper-2015-11-07.jpg"
+       localCsvFile       string = "sample/sample_data.csv"
+       localJSONFile      string = "sample/sample_json.json"
+       localJSONLinesFile string = "sample/sample_json_lines.json"
+       htmlLocalFile      string = "sample/The Go Programming Language.html"
+)
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/copy_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/copy_object.go
new file mode 100644 (file)
index 0000000..c25dcd5
--- /dev/null
@@ -0,0 +1,138 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// CopyObjectSample shows the copy files usage
+func CopyObjectSample() {
+       // Create a bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create an object
+       err = bucket.PutObjectFromFile(objectKey, localFile)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1: Copy an existing object
+       var descObjectKey = "descobject"
+       _, err = bucket.CopyObject(objectKey, descObjectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2: Copy an existing object to another existing object
+       _, err = bucket.CopyObject(objectKey, descObjectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = bucket.DeleteObject(descObjectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 3: Copy file with constraints. When the constraints are met, the copy executes. otherwise the copy does not execute.
+       // constraints are not met, copy does not execute
+       _, err = bucket.CopyObject(objectKey, descObjectKey, oss.CopySourceIfModifiedSince(futureDate))
+       if err == nil {
+               HandleError(err)
+       }
+       fmt.Println("CopyObjectError:", err)
+       // Constraints are met, the copy executes
+       _, err = bucket.CopyObject(objectKey, descObjectKey, oss.CopySourceIfUnmodifiedSince(futureDate))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 4: Specify the properties when copying. The MetadataDirective needs to be MetaReplace
+       options := []oss.Option{
+               oss.Expires(futureDate),
+               oss.Meta("myprop", "mypropval"),
+               oss.MetadataDirective(oss.MetaReplace)}
+       _, err = bucket.CopyObject(objectKey, descObjectKey, options...)
+       if err != nil {
+               HandleError(err)
+       }
+
+       meta, err := bucket.GetObjectDetailedMeta(descObjectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("meta:", meta)
+
+       // Case 5: When the source file is the same as the target file, the copy could be used to update metadata
+       options = []oss.Option{
+               oss.Expires(futureDate),
+               oss.Meta("myprop", "mypropval"),
+               oss.MetadataDirective(oss.MetaReplace)}
+
+       _, err = bucket.CopyObject(objectKey, objectKey, options...)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("meta:", meta)
+
+       // Case 6: Big file's multipart copy. It supports concurrent copy with resumable upload
+       // copy file with multipart. The part size is 100K. By default one routine is used without resumable upload
+       err = bucket.CopyFile(bucketName, objectKey, descObjectKey, 100*1024)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Part size is 100K and three coroutines for the concurrent copy
+       err = bucket.CopyFile(bucketName, objectKey, descObjectKey, 100*1024, oss.Routines(3))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Part size is 100K and three coroutines for the concurrent copy with resumable upload
+       err = bucket.CopyFile(bucketName, objectKey, descObjectKey, 100*1024, oss.Routines(3), oss.Checkpoint(true, ""))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Specify the checkpoint file path. If the checkpoint file path is not specified, the current folder is used.
+       err = bucket.CopyFile(bucketName, objectKey, descObjectKey, 100*1024, oss.Checkpoint(true, localFile+".cp"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 7: Set the storage classes.OSS provides three storage classes: Standard, Infrequent Access, and Archive.
+       // Copy a object in the same bucket, and set object's storage-class to Archive.
+       _, err = bucket.CopyObject(objectKey, objectKey+"DestArchive", oss.ObjectStorageClass("Archive"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 8: Copy object with tagging, the value of tagging directive is REPLACE
+       tag1 := oss.Tag{
+               Key:   "key1",
+               Value: "value1",
+       }
+       tag2 := oss.Tag{
+               Key:   "key2",
+               Value: "value2",
+       }
+       tagging := oss.Tagging{
+               Tags: []oss.Tag{tag1, tag2},
+       }
+       _, err = bucket.CopyObject(objectKey, objectKey+"WithTagging", oss.SetTagging(tagging), oss.TaggingDirective(oss.TaggingReplace))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("CopyObjectSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/create_bucket.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/create_bucket.go
new file mode 100644 (file)
index 0000000..976e5c5
--- /dev/null
@@ -0,0 +1,50 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// CreateBucketSample shows how to create bucket
+func CreateBucketSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       DeleteTestBucketAndObject(bucketName)
+
+       // Case 1: Create a bucket with default parameters
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2: Create the bucket with ACL
+       err = client.CreateBucket(bucketName, oss.ACL(oss.ACLPublicRead))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 3: Repeat the same bucket. OSS will not return error, but just no op. The ACL is not updated.
+       err = client.CreateBucket(bucketName, oss.ACL(oss.ACLPublicReadWrite))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("CreateBucketSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/delete_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/delete_object.go
new file mode 100644 (file)
index 0000000..d4d898e
--- /dev/null
@@ -0,0 +1,108 @@
+package sample
+
+import (
+       "fmt"
+       "strings"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// DeleteObjectSample shows how to delete single file or multiple files
+func DeleteObjectSample() {
+       // Create a bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       var val = "抽刀断水水更流,举杯销愁愁更愁。 人生在世不称意,明朝散发弄扁舟。"
+
+       // Case 1: Delete an object
+       err = bucket.PutObject(objectKey, strings.NewReader(val))
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = bucket.DeleteObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2: Delete multiple Objects
+       err = bucket.PutObject(objectKey+"1", strings.NewReader(val))
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = bucket.PutObject(objectKey+"2", strings.NewReader(val))
+       if err != nil {
+               HandleError(err)
+       }
+
+       delRes, err := bucket.DeleteObjects([]string{objectKey + "1", objectKey + "2"})
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Del Res:", delRes)
+
+       lsRes, err := bucket.ListObjects()
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Objects:", getObjectsFormResponse(lsRes))
+
+       // Case 3: Delete multiple objects and it will return deleted objects in detail mode which is by default.
+       err = bucket.PutObject(objectKey+"1", strings.NewReader(val))
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = bucket.PutObject(objectKey+"2", strings.NewReader(val))
+       if err != nil {
+               HandleError(err)
+       }
+
+       delRes, err = bucket.DeleteObjects([]string{objectKey + "1", objectKey + "2"},
+               oss.DeleteObjectsQuiet(false))
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Detail Del Res:", delRes)
+
+       lsRes, err = bucket.ListObjects()
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Objects:", getObjectsFormResponse(lsRes))
+
+       // Case 4: Delete multiple objects and returns undeleted objects in quiet mode
+       err = bucket.PutObject(objectKey+"1", strings.NewReader(val))
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = bucket.PutObject(objectKey+"2", strings.NewReader(val))
+       if err != nil {
+               HandleError(err)
+       }
+
+       delRes, err = bucket.DeleteObjects([]string{objectKey + "1", objectKey + "2"}, oss.DeleteObjectsQuiet(true))
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Sample Del Res:", delRes)
+
+       lsRes, err = bucket.ListObjects()
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Objects:", getObjectsFormResponse(lsRes))
+
+       // Delete object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("DeleteObjectSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/get_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/get_object.go
new file mode 100644 (file)
index 0000000..019e86b
--- /dev/null
@@ -0,0 +1,156 @@
+package sample
+
+import (
+       "bytes"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// GetObjectSample shows the streaming download, range download and resumable download.
+func GetObjectSample() {
+       // Create bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Upload the object
+       err = bucket.PutObjectFromFile(objectKey, localFile)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1: Download the object into ReadCloser(). The body needs to be closed
+       body, err := bucket.GetObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       data, err := ioutil.ReadAll(body)
+       body.Close()
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("size of data is: ", len(data))
+
+       // Case 2: Download in the range of object.
+       body, err = bucket.GetObject(objectKey, oss.Range(15, 19))
+       if err != nil {
+               HandleError(err)
+       }
+       data, err = ioutil.ReadAll(body)
+       body.Close()
+       fmt.Println("the range of data is: ", string(data))
+
+       // Case 3: Download the object to byte array. This is for small object.
+       buf := new(bytes.Buffer)
+       body, err = bucket.GetObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       io.Copy(buf, body)
+       body.Close()
+
+       // Case 4: Download the object to local file. The file handle needs to be specified
+       fd, err := os.OpenFile("mynewfile-1.jpg", os.O_WRONLY|os.O_CREATE, 0660)
+       if err != nil {
+               HandleError(err)
+       }
+       defer fd.Close()
+
+       body, err = bucket.GetObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       io.Copy(fd, body)
+       body.Close()
+
+       // Case 5: Download the object to local file with file name specified
+       err = bucket.GetObjectToFile(objectKey, "mynewfile-2.jpg")
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 6: Get the object with contraints. When contraints are met, download the file. Otherwise return precondition error
+       // last modified time constraint is met, download the file
+       body, err = bucket.GetObject(objectKey, oss.IfModifiedSince(pastDate))
+       if err != nil {
+               HandleError(err)
+       }
+       body.Close()
+
+       // Last modified time contraint is not met, do not download the file
+       _, err = bucket.GetObject(objectKey, oss.IfUnmodifiedSince(pastDate))
+       if err == nil {
+               HandleError(fmt.Errorf("This result is not the expected result"))
+       }
+       // body.Close()
+
+       meta, err := bucket.GetObjectDetailedMeta(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       etag := meta.Get(oss.HTTPHeaderEtag)
+       // Check the content, etag contraint is met, download the file
+       body, err = bucket.GetObject(objectKey, oss.IfMatch(etag))
+       if err != nil {
+               HandleError(err)
+       }
+       body.Close()
+
+       // Check the content, etag contraint is not met, do not download the file
+       _, err = bucket.GetObject(objectKey, oss.IfNoneMatch(etag))
+       if err == nil {
+               HandleError(fmt.Errorf("This result is not the expected result"))
+       }
+       // body.Close()
+
+       // Case 7: Big file's multipart download, concurrent and resumable download is supported.
+       // multipart download with part size 100KB. By default single coroutine is used and no checkpoint
+       err = bucket.DownloadFile(objectKey, "mynewfile-3.jpg", 100*1024)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Part size is 100K and 3 coroutines are used
+       err = bucket.DownloadFile(objectKey, "mynewfile-3.jpg", 100*1024, oss.Routines(3))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Part size is 100K and 3 coroutines with checkpoint
+       err = bucket.DownloadFile(objectKey, "mynewfile-3.jpg", 100*1024, oss.Routines(3), oss.Checkpoint(true, ""))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Specify the checkpoint file path to record which parts have been downloaded.
+       // This file path can be specified by the 2nd parameter of Checkpoint, it will be the download directory if the file path is empty.
+       err = bucket.DownloadFile(objectKey, "mynewfile-3.jpg", 100*1024, oss.Checkpoint(true, "mynewfile.cp"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 8: Use GZIP encoding for downloading the file, GetObject/GetObjectToFile are the same.
+       err = bucket.PutObjectFromFile(objectKey, htmlLocalFile)
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = bucket.GetObjectToFile(objectKey, "myhtml.gzip", oss.AcceptEncoding("gzip"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete the object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("GetObjectSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_buckets.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_buckets.go
new file mode 100644 (file)
index 0000000..2d50826
--- /dev/null
@@ -0,0 +1,129 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// ListBucketsSample shows the list bucket, including default and specified parameters.
+func ListBucketsSample() {
+       var myBuckets = []string{
+               "my-bucket-1",
+               "my-bucket-11",
+               "my-bucket-2",
+               "my-bucket-21",
+               "my-bucket-22",
+               "my-bucket-3",
+               "my-bucket-31",
+               "my-bucket-32"}
+
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Remove other bucket
+       lbr, err := client.ListBuckets()
+       if err != nil {
+               HandleError(err)
+       }
+
+       for _, bucket := range lbr.Buckets {
+               err = client.DeleteBucket(bucket.Name)
+               if err != nil {
+                       //HandleError(err)
+               }
+       }
+
+       // Create bucket
+       for _, bucketName := range myBuckets {
+               err = client.CreateBucket(bucketName)
+               if err != nil {
+                       HandleError(err)
+               }
+       }
+
+       // Case 1: Use default parameter
+       lbr, err = client.ListBuckets()
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("my buckets:", lbr.Buckets)
+
+       // Case 2: Specify the max keys : 3
+       lbr, err = client.ListBuckets(oss.MaxKeys(3))
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("my buckets max num:", lbr.Buckets)
+
+       // Case 3: Specify the prefix of buckets.
+       lbr, err = client.ListBuckets(oss.Prefix("my-bucket-2"))
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("my buckets prefix :", lbr.Buckets)
+
+       // Case 4: Specify the marker to return from a certain one
+       lbr, err = client.ListBuckets(oss.Marker("my-bucket-22"))
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("my buckets marker :", lbr.Buckets)
+
+       // Case 5: Specify max key and list all buckets with paging, return 3 items each time.
+       marker := oss.Marker("")
+       for {
+               lbr, err = client.ListBuckets(oss.MaxKeys(3), marker)
+               if err != nil {
+                       HandleError(err)
+               }
+               marker = oss.Marker(lbr.NextMarker)
+               fmt.Println("my buckets page :", lbr.Buckets)
+               if !lbr.IsTruncated {
+                       break
+               }
+       }
+
+       // Case 6: List bucket with marker and max key; return 3 items each time.
+       marker = oss.Marker("my-bucket-22")
+       for {
+               lbr, err = client.ListBuckets(oss.MaxKeys(3), marker)
+               if err != nil {
+                       HandleError(err)
+               }
+               marker = oss.Marker(lbr.NextMarker)
+               fmt.Println("my buckets marker&page :", lbr.Buckets)
+               if !lbr.IsTruncated {
+                       break
+               }
+       }
+
+       // Case 7: List bucket with prefix and max key, return 3 items each time.
+       pre := oss.Prefix("my-bucket-2")
+       marker = oss.Marker("")
+       for {
+               lbr, err = client.ListBuckets(oss.MaxKeys(3), pre, marker)
+               if err != nil {
+                       HandleError(err)
+               }
+               pre = oss.Prefix(lbr.Prefix)
+               marker = oss.Marker(lbr.NextMarker)
+               fmt.Println("my buckets prefix&page :", lbr.Buckets)
+               if !lbr.IsTruncated {
+                       break
+               }
+       }
+
+       // Delete bucket
+       for _, bucketName := range myBuckets {
+               err = client.DeleteBucket(bucketName)
+               if err != nil {
+                       HandleError(err)
+               }
+       }
+
+       fmt.Println("ListsBucketSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_objects.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_objects.go
new file mode 100644 (file)
index 0000000..d0b0836
--- /dev/null
@@ -0,0 +1,148 @@
+package sample
+
+import (
+       "fmt"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// ListObjectsSample shows the file list, including default and specified parameters.
+func ListObjectsSample() {
+       var myObjects = []Object{
+               {"my-object-1", ""},
+               {"my-object-11", ""},
+               {"my-object-2", ""},
+               {"my-object-21", ""},
+               {"my-object-22", ""},
+               {"my-object-3", ""},
+               {"my-object-31", ""},
+               {"my-object-32", ""}}
+
+       // Create bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create objects
+       err = CreateObjects(bucket, myObjects)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1: Use default parameters
+       lor, err := bucket.ListObjects()
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("my objects:", getObjectsFormResponse(lor))
+
+       // Case 2: Specify max keys
+       lor, err = bucket.ListObjects(oss.MaxKeys(3))
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("my objects max num:", getObjectsFormResponse(lor))
+
+       // Case 3: Specify prefix of objects
+       lor, err = bucket.ListObjects(oss.Prefix("my-object-2"))
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("my objects prefix :", getObjectsFormResponse(lor))
+
+       // Case 4: Specify the marker
+       lor, err = bucket.ListObjects(oss.Marker("my-object-22"))
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("my objects marker :", getObjectsFormResponse(lor))
+
+       // Case 5: List object with paging. each page has 3 objects
+       marker := oss.Marker("")
+       for {
+               lor, err = bucket.ListObjects(oss.MaxKeys(3), marker)
+               if err != nil {
+                       HandleError(err)
+               }
+               marker = oss.Marker(lor.NextMarker)
+               fmt.Println("my objects page :", getObjectsFormResponse(lor))
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Case 6: List object with paging , marker and max keys; return 3 items each time.
+       marker = oss.Marker("my-object-22")
+       for {
+               lor, err = bucket.ListObjects(oss.MaxKeys(3), marker)
+               if err != nil {
+                       HandleError(err)
+               }
+               marker = oss.Marker(lor.NextMarker)
+               fmt.Println("my objects marker&page :", getObjectsFormResponse(lor))
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       // Case 7: List object with paging , with prefix and max keys; return 2 items each time.
+       pre := oss.Prefix("my-object-2")
+       marker = oss.Marker("")
+       for {
+               lor, err = bucket.ListObjects(oss.MaxKeys(2), marker, pre)
+               if err != nil {
+                       HandleError(err)
+               }
+               pre = oss.Prefix(lor.Prefix)
+               marker = oss.Marker(lor.NextMarker)
+               fmt.Println("my objects prefix&page :", getObjectsFormResponse(lor))
+               if !lor.IsTruncated {
+                       break
+               }
+       }
+
+       err = DeleteObjects(bucket, myObjects)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 8: Combine the prefix and delimiter for grouping. ListObjectsResponse.Objects is the objects returned.
+       // ListObjectsResponse.CommonPrefixes is the common prefixes returned.
+       myObjects = []Object{
+               {"fun/test.txt", ""},
+               {"fun/test.jpg", ""},
+               {"fun/movie/001.avi", ""},
+               {"fun/movie/007.avi", ""},
+               {"fun/music/001.mp3", ""},
+               {"fun/music/001.mp3", ""}}
+
+       // Create object
+       err = CreateObjects(bucket, myObjects)
+       if err != nil {
+               HandleError(err)
+       }
+
+       lor, err = bucket.ListObjects(oss.Prefix("fun/"), oss.Delimiter("/"))
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("my objects prefix :", getObjectsFormResponse(lor),
+               "common prefixes:", lor.CommonPrefixes)
+
+       // Delete object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("ListObjectsSample completed")
+}
+
+func getObjectsFormResponse(lor oss.ListObjectsResult) string {
+       var output string
+       for _, object := range lor.Objects {
+               output += object.Key + "  "
+       }
+       return output
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/livechannel.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/livechannel.go
new file mode 100644 (file)
index 0000000..d258be3
--- /dev/null
@@ -0,0 +1,445 @@
+package sample
+
+import (
+       "fmt"
+       "io/ioutil"
+       "time"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// CreateLiveChannelSample Samples for create a live-channel
+func CreateLiveChannelSample() {
+       channelName := "create-livechannel"
+       //create bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1 - Create live-channel with Completely configure
+       config := oss.LiveChannelConfiguration{
+               Description: "sample-for-livechannel", //description information, up to 128 bytes
+               Status:      "enabled",                //enabled or disabled
+               Target: oss.LiveChannelTarget{
+                       Type:         "HLS",                          //the type of object, only supports HLS, required
+                       FragDuration: 10,                             //the length of each ts object (in seconds), in the range [1,100], default: 5
+                       FragCount:    4,                              //the number of ts objects in the m3u8 object, in the range of [1,100], default: 3
+                       PlaylistName: "test-get-channel-status.m3u8", //the name of m3u8 object, which must end with ".m3u8" and the length range is [6,128],default: playlist.m3u8
+               },
+       }
+
+       result, err := bucket.CreateLiveChannel(channelName, config)
+       if err != nil {
+               HandleError(err)
+       }
+
+       playURL := result.PlayUrls[0]
+       publishURL := result.PublishUrls[0]
+       fmt.Printf("create livechannel:%s  with config respones: playURL:%s, publishURL: %s\n", channelName, playURL, publishURL)
+
+       // Case 2 - Create live-channel only specified type of target which is required
+       simpleCfg := oss.LiveChannelConfiguration{
+               Target: oss.LiveChannelTarget{
+                       Type: "HLS",
+               },
+       }
+       result, err = bucket.CreateLiveChannel(channelName, simpleCfg)
+       if err != nil {
+               HandleError(err)
+       }
+       playURL = result.PlayUrls[0]
+       publishURL = result.PublishUrls[0]
+       fmt.Printf("create livechannel:%s  with  simple config respones: playURL:%s, publishURL: %s\n", channelName, playURL, publishURL)
+
+       err = DeleteTestBucketAndLiveChannel(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("PutObjectSample completed")
+}
+
+// PutLiveChannelStatusSample Set the status of the live-channel sample: enabled/disabled
+func PutLiveChannelStatusSample() {
+       channelName := "put-livechannel-status"
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       config := oss.LiveChannelConfiguration{
+               Target: oss.LiveChannelTarget{
+                       Type: "HLS", //the type of object, only supports HLS, required
+               },
+       }
+
+       _, err = bucket.CreateLiveChannel(channelName, config)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1 - Set the status of live-channel to disabled
+       err = bucket.PutLiveChannelStatus(channelName, "disabled")
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2 - Set the status of live-channel to enabled
+       err = bucket.PutLiveChannelStatus(channelName, "enabled")
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = DeleteTestBucketAndLiveChannel(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("PutLiveChannelStatusSample completed")
+}
+
+// PostVodPlayListSample Sample for generate playlist
+func PostVodPlayListSample() {
+       channelName := "post-vod-playlist"
+       playlistName := "playlist.m3u8"
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       config := oss.LiveChannelConfiguration{
+               Target: oss.LiveChannelTarget{
+                       Type:         "HLS", //the type of object, only supports HLS, required
+                       PlaylistName: "playlist.m3u8",
+               },
+       }
+
+       _, err = bucket.CreateLiveChannel(channelName, config)
+       if err != nil {
+               HandleError(err)
+       }
+
+       //This stage you can push live stream, and after that you could generator playlist
+
+       endTime := time.Now().Add(-1 * time.Minute)
+       startTime := endTime.Add(-60 * time.Minute)
+       err = bucket.PostVodPlaylist(channelName, playlistName, startTime, endTime)
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = DeleteTestBucketAndLiveChannel(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("PostVodPlayListSampleSample completed")
+}
+
+// GetVodPlayListSample Sample for generate playlist and return the content of the playlist
+func GetVodPlayListSample() {
+       channelName := "get-vod-playlist"
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       config := oss.LiveChannelConfiguration{
+               Target: oss.LiveChannelTarget{
+                       Type:         "HLS", //the type of object, only supports HLS, required
+                       PlaylistName: "playlist.m3u8",
+               },
+       }
+
+       _, err = bucket.CreateLiveChannel(channelName, config)
+       if err != nil {
+               HandleError(err)
+       }
+
+       //This stage you can push live stream, and after that you could generator playlist
+
+       endTime := time.Now().Add(-1 * time.Minute)
+       startTime := endTime.Add(-60 * time.Minute)
+       body, err := bucket.GetVodPlaylist(channelName, startTime, endTime)
+       if err != nil {
+               HandleError(err)
+       }
+       defer body.Close()
+
+       data, err := ioutil.ReadAll(body)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Printf("content of playlist is:%v\n", string(data))
+
+       err = DeleteTestBucketAndLiveChannel(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("PostVodPlayListSampleSample completed")
+}
+
+// GetLiveChannelStatSample Sample for get the state of live-channel
+func GetLiveChannelStatSample() {
+       channelName := "get-livechannel-stat"
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       config := oss.LiveChannelConfiguration{
+               Target: oss.LiveChannelTarget{
+                       Type: "HLS", //the type of object, only supports HLS, required
+               },
+       }
+
+       _, err = bucket.CreateLiveChannel(channelName, config)
+       if err != nil {
+               HandleError(err)
+       }
+
+       stat, err := bucket.GetLiveChannelStat(channelName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       status := stat.Status
+       connectedTime := stat.ConnectedTime
+       remoteAddr := stat.RemoteAddr
+
+       audioBW := stat.Audio.Bandwidth
+       audioCodec := stat.Audio.Codec
+       audioSampleRate := stat.Audio.SampleRate
+
+       videoBW := stat.Video.Bandwidth
+       videoFrameRate := stat.Video.FrameRate
+       videoHeight := stat.Video.Height
+       videoWidth := stat.Video.Width
+
+       fmt.Printf("get channel stat:(%v, %v,%v, %v), audio(%v, %v, %v), video(%v, %v, %v, %v)\n", channelName, status, connectedTime, remoteAddr, audioBW, audioCodec, audioSampleRate, videoBW, videoFrameRate, videoHeight, videoWidth)
+
+       err = DeleteTestBucketAndLiveChannel(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("GetLiveChannelStatSample completed")
+}
+
+// GetLiveChannelInfoSample Sample for get the configuration infomation of live-channel
+func GetLiveChannelInfoSample() {
+       channelName := "get-livechannel-info"
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       config := oss.LiveChannelConfiguration{
+               Target: oss.LiveChannelTarget{
+                       Type: "HLS", //the type of object, only supports HLS, required
+               },
+       }
+
+       _, err = bucket.CreateLiveChannel(channelName, config)
+       if err != nil {
+               HandleError(err)
+       }
+
+       info, err := bucket.GetLiveChannelInfo(channelName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       desc := info.Description
+       status := info.Status
+       fragCount := info.Target.FragCount
+       fragDuation := info.Target.FragDuration
+       playlistName := info.Target.PlaylistName
+       targetType := info.Target.Type
+
+       fmt.Printf("get channel stat:(%v,%v, %v), target(%v, %v, %v, %v)\n", channelName, desc, status, fragCount, fragDuation, playlistName, targetType)
+
+       err = DeleteTestBucketAndLiveChannel(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("GetLiveChannelInfoSample completed")
+}
+
+// GetLiveChannelHistorySample  Sample for get push records of live-channel
+func GetLiveChannelHistorySample() {
+       channelName := "get-livechannel-info"
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       config := oss.LiveChannelConfiguration{
+               Target: oss.LiveChannelTarget{
+                       Type: "HLS", //the type of object, only supports HLS, required
+               },
+       }
+
+       _, err = bucket.CreateLiveChannel(channelName, config)
+       if err != nil {
+               HandleError(err)
+       }
+
+       //at most return up to lastest 10 push records
+       history, err := bucket.GetLiveChannelHistory(channelName)
+       for _, record := range history.Record {
+               remoteAddr := record.RemoteAddr
+               startTime := record.StartTime
+               endTime := record.EndTime
+               fmt.Printf("get channel:%s history:(%v, %v, %v)\n", channelName, remoteAddr, startTime, endTime)
+       }
+
+       err = DeleteTestBucketAndLiveChannel(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("GetLiveChannelHistorySample completed")
+}
+
+// ListLiveChannelSample Samples for list live-channels with specified bucket name
+func ListLiveChannelSample() {
+       channelName := "list-livechannel"
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       config := oss.LiveChannelConfiguration{
+               Target: oss.LiveChannelTarget{
+                       Type: "HLS", //the type of object, only supports HLS, required
+               },
+       }
+
+       _, err = bucket.CreateLiveChannel(channelName, config)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1: list all the live-channels
+       marker := ""
+       for {
+               // Set the marker value, the first time is "", the value of NextMarker that returned should as the marker in the next time
+               // At most return up to lastest 100 live-channels if "max-keys" is not specified
+               result, err := bucket.ListLiveChannel(oss.Marker(marker))
+               if err != nil {
+                       HandleError(err)
+               }
+
+               for _, channel := range result.LiveChannel {
+                       fmt.Printf("list livechannel: (%v, %v, %v, %v, %v, %v)\n", channel.Name, channel.Status, channel.Description, channel.LastModified, channel.PlayUrls[0], channel.PublishUrls[0])
+               }
+
+               if result.IsTruncated {
+                       marker = result.NextMarker
+               } else {
+                       break
+               }
+       }
+
+       // Case 2: Use the parameter "max-keys" to specify the maximum number of records returned, the value of max-keys cannot exceed 1000
+       // if "max-keys" the default value is 100
+       result, err := bucket.ListLiveChannel(oss.MaxKeys(10))
+       if err != nil {
+               HandleError(err)
+       }
+       for _, channel := range result.LiveChannel {
+               fmt.Printf("list livechannel: (%v, %v, %v, %v, %v, %v)\n", channel.Name, channel.Status, channel.Description, channel.LastModified, channel.PlayUrls[0], channel.PublishUrls[0])
+       }
+
+       // Case 3: Only list the live-channels with the value of parameter "prefix" as prefix
+       // max-keys, prefix, maker parameters can be combined
+       result, err = bucket.ListLiveChannel(oss.MaxKeys(10), oss.Prefix("list-"))
+       if err != nil {
+               HandleError(err)
+       }
+       for _, channel := range result.LiveChannel {
+               fmt.Printf("list livechannel: (%v, %v, %v, %v, %v, %v)\n", channel.Name, channel.Status, channel.Description, channel.LastModified, channel.PlayUrls[0], channel.PublishUrls[0])
+       }
+
+       err = DeleteTestBucketAndLiveChannel(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("ListLiveChannelSample completed")
+}
+
+// DeleteLiveChannelSample Sample for delete live-channel
+func DeleteLiveChannelSample() {
+       channelName := "delete-livechannel"
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       config := oss.LiveChannelConfiguration{
+               Target: oss.LiveChannelTarget{
+                       Type: "HLS", //the type of object, only supports HLS, required
+               },
+       }
+
+       _, err = bucket.CreateLiveChannel(channelName, config)
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = bucket.DeleteLiveChannel(channelName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = DeleteTestBucketAndLiveChannel(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("DeleteLiveChannelSample completed")
+}
+
+// SignRtmpURLSample Sample for generate a RTMP push-stream signature URL for the trusted user to push the RTMP stream to the live channel.
+func SignRtmpURLSample() {
+       channelName := "sign-rtmp-url"
+       playlistName := "playlist.m3u8"
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       config := oss.LiveChannelConfiguration{
+               Target: oss.LiveChannelTarget{
+                       Type:         "HLS", //the type of object, only supports HLS, required
+                       PlaylistName: "playlist.m3u8",
+               },
+       }
+
+       result, err := bucket.CreateLiveChannel(channelName, config)
+       if err != nil {
+               HandleError(err)
+       }
+
+       playURL := result.PlayUrls[0]
+       publishURL := result.PublishUrls[0]
+       fmt.Printf("livechannel:%s, playURL:%s, publishURL: %s\n", channelName, playURL, publishURL)
+
+       signedRtmpURL, err := bucket.SignRtmpURL(channelName, playlistName, 3600)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Printf("livechannel:%s, sinedRtmpURL: %s\n", channelName, signedRtmpURL)
+
+       err = DeleteTestBucketAndLiveChannel(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("SignRtmpURLSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/new_bucket.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/new_bucket.go
new file mode 100644 (file)
index 0000000..2a375c1
--- /dev/null
@@ -0,0 +1,50 @@
+package sample
+
+import (
+       "fmt"
+       "strings"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// NewBucketSample shows how to initialize client and bucket
+func NewBucketSample() {
+       // New client
+       client, err := oss.New(endpoint, accessID, accessKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create bucket
+       err = client.CreateBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // New bucket
+       bucket, err := client.Bucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Put object, uploads an object
+       var objectName = "myobject"
+       err = bucket.PutObject(objectName, strings.NewReader("MyObjectValue"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete object, deletes an object
+       err = bucket.DeleteObject(objectName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete bucket
+       err = client.DeleteBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("NewBucketSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_acl.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_acl.go
new file mode 100755 (executable)
index 0000000..167de79
--- /dev/null
@@ -0,0 +1,44 @@
+package sample
+
+import (
+       "fmt"
+       "strings"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// ObjectACLSample shows how to set and get object ACL
+func ObjectACLSample() {
+       // Create bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create object
+       err = bucket.PutObject(objectKey, strings.NewReader("YoursObjectValue"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1: Set bucket ACL, valid ACLs are ACLPrivate、ACLPublicRead、ACLPublicReadWrite
+       err = bucket.SetObjectACL(objectKey, oss.ACLPrivate)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get object ACL, returns one of the three values: private、public-read、public-read-write
+       goar, err := bucket.GetObjectACL(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Object ACL:", goar.ACL)
+
+       // Delete object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("ObjectACLSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_meta.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_meta.go
new file mode 100755 (executable)
index 0000000..6150d6b
--- /dev/null
@@ -0,0 +1,73 @@
+package sample
+
+import (
+       "fmt"
+       "strings"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// ObjectMetaSample shows how to get and set the object metadata
+func ObjectMetaSample() {
+       // Create bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete object
+       err = bucket.PutObject(objectKey, strings.NewReader("YoursObjectValue"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 0: Set bucket meta. one or more properties could be set
+       // Note: Meta is case insensitive
+       options := []oss.Option{
+               oss.Expires(futureDate),
+               oss.Meta("myprop", "mypropval")}
+       err = bucket.SetObjectMeta(objectKey, options...)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1: Get the object metadata. Only return basic meta information includes ETag, size and last modified.
+       props, err := bucket.GetObjectMeta(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Object Meta:", props)
+
+       // Case 2: Get all the detailed object meta including custom meta
+       props, err = bucket.GetObjectDetailedMeta(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Expires:", props.Get("Expires"))
+
+       // Case 3: Get the object's all metadata with contraints. When constraints are met, return the metadata.
+       props, err = bucket.GetObjectDetailedMeta(objectKey, oss.IfUnmodifiedSince(futureDate))
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("MyProp:", props.Get("X-Oss-Meta-Myprop"))
+
+       _, err = bucket.GetObjectDetailedMeta(objectKey, oss.IfModifiedSince(futureDate))
+       if err == nil {
+               HandleError(err)
+       }
+
+       goar, err := bucket.GetObjectACL(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Object ACL:", goar.ACL)
+
+       // Delete object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("ObjectMetaSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_tagging.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_tagging.go
new file mode 100644 (file)
index 0000000..67271e9
--- /dev/null
@@ -0,0 +1,75 @@
+package sample
+
+import (
+       "fmt"
+       "strings"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// ObjectTaggingSample shows how to set and get object Tagging
+func ObjectTaggingSample() {
+       // Create bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create object
+       err = bucket.PutObject(objectKey, strings.NewReader("ObjectTaggingSample"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 1: Set Tagging of object
+       tag1 := oss.Tag{
+               Key:   "key1",
+               Value: "value1",
+       }
+       tag2 := oss.Tag{
+               Key:   "key2",
+               Value: "value2",
+       }
+       tagging := oss.Tagging{
+               Tags: []oss.Tag{tag1, tag2},
+       }
+       err = bucket.PutObjectTagging(objectKey, tagging)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2: Get Tagging of object
+       taggingResult, err := bucket.GetObjectTagging(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Printf("Object Tagging: %v\n", taggingResult)
+
+       tag3 := oss.Tag{
+               Key:   "key3",
+               Value: "value3",
+       }
+
+       // Case 3: Put object with tagging
+       tagging = oss.Tagging{
+               Tags: []oss.Tag{tag1, tag2, tag3},
+       }
+       err = bucket.PutObject(objectKey, strings.NewReader("ObjectTaggingSample"), oss.SetTagging(tagging))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 4: Delete Tagging of object
+       err = bucket.DeleteObjectTagging(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("ObjectACLSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/put_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/put_object.go
new file mode 100755 (executable)
index 0000000..667eb2d
--- /dev/null
@@ -0,0 +1,145 @@
+package sample
+
+import (
+       "bytes"
+       "encoding/base64"
+       "encoding/json"
+       "fmt"
+       "os"
+       "strings"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// PutObjectSample illustrates two methods for uploading a file: simple upload and multipart upload.
+func PutObjectSample() {
+       // Create bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       var val = "花间一壶酒,独酌无相亲。 举杯邀明月,对影成三人。"
+
+       // Case 1: Upload an object from a string
+       err = bucket.PutObject(objectKey, strings.NewReader(val))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 2: Upload an object whose value is a byte[]
+       err = bucket.PutObject(objectKey, bytes.NewReader([]byte(val)))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 3: Upload the local file with file handle, user should open the file at first.
+       fd, err := os.Open(localFile)
+       if err != nil {
+               HandleError(err)
+       }
+       defer fd.Close()
+
+       err = bucket.PutObject(objectKey, fd)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 4: Upload an object with local file name, user need not open the file.
+       err = bucket.PutObjectFromFile(objectKey, localFile)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 5: Upload an object with specified properties, PutObject/PutObjectFromFile/UploadFile also support this feature.
+       options := []oss.Option{
+               oss.Expires(futureDate),
+               oss.ObjectACL(oss.ACLPublicRead),
+               oss.Meta("myprop", "mypropval"),
+       }
+       err = bucket.PutObject(objectKey, strings.NewReader(val), options...)
+       if err != nil {
+               HandleError(err)
+       }
+
+       props, err := bucket.GetObjectDetailedMeta(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("Object Meta:", props)
+
+       // Case 6: Upload an object with sever side encrpytion kms and kms id specified
+       err = bucket.PutObject(objectKey, strings.NewReader(val), oss.ServerSideEncryption("KMS"), oss.ServerSideEncryptionKeyID(kmsID))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 7: Upload an object with callback
+       callbackMap := map[string]string{}
+       callbackMap["callbackUrl"] = "http://oss-demo.aliyuncs.com:23450"
+       callbackMap["callbackHost"] = "oss-cn-hangzhou.aliyuncs.com"
+       callbackMap["callbackBody"] = "filename=${object}&size=${size}&mimeType=${mimeType}"
+       callbackMap["callbackBodyType"] = "application/x-www-form-urlencoded"
+
+       callbackBuffer := bytes.NewBuffer([]byte{})
+       callbackEncoder := json.NewEncoder(callbackBuffer)
+       //do not encode '&' to "\u0026"
+       callbackEncoder.SetEscapeHTML(false)
+       err = callbackEncoder.Encode(callbackMap)
+       if err != nil {
+               HandleError(err)
+       }
+
+       callbackVal := base64.StdEncoding.EncodeToString(callbackBuffer.Bytes())
+       err = bucket.PutObject(objectKey, strings.NewReader(val), oss.Callback(callbackVal))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 8: Big file's multipart upload. It supports concurrent upload with resumable upload.
+       // multipart upload with 100K as part size. By default 1 coroutine is used and no checkpoint is used.
+       err = bucket.UploadFile(objectKey, localFile, 100*1024)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Part size is 100K and 3 coroutines are used
+       err = bucket.UploadFile(objectKey, localFile, 100*1024, oss.Routines(3))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Part size is 100K and 3 coroutines with checkpoint
+       err = bucket.UploadFile(objectKey, localFile, 100*1024, oss.Routines(3), oss.Checkpoint(true, ""))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Specify the local file path for checkpoint files.
+       // the 2nd parameter of Checkpoint can specify the file path, when the file path is empty, it will upload the directory.
+       err = bucket.UploadFile(objectKey, localFile, 100*1024, oss.Checkpoint(true, localFile+".cp"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Case 9: Set the storage classes.OSS provides three storage classes: Standard, Infrequent Access, and Archive.
+       // Supported APIs: PutObject, CopyObject, UploadFile, AppendObject...
+       err = bucket.PutObject(objectKey, strings.NewReader(val), oss.ObjectStorageClass("IA"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Upload a local file, and set the object's storage-class to 'Archive'.
+       err = bucket.UploadFile(objectKey, localFile, 100*1024, oss.ObjectStorageClass("Archive"))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("PutObjectSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sample_data.csv b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sample_data.csv
new file mode 100644 (file)
index 0000000..0c3f3a9
--- /dev/null
@@ -0,0 +1,304 @@
+Year,StateAbbr,StateDesc,CityName,GeographicLevel,DataSource,Category,UniqueID,Measure,Data_Value_Unit,DataValueTypeID,Data_Value_Type,Data_Value,Low_Confidence_Limit,High_Confidence_Limit,Data_Value_Footnote_Symbol,Data_Value_Footnote,PopulationCount,GeoLocation,CategoryID,MeasureId,CityFIPS,TractFIPS,Short_Question_Text
+2015,US,United States,,US,BRFSS,Prevention,59,Current lack of health insurance among adults aged 18–64 Years,%,AgeAdjPrv,Age-adjusted prevalence,15.4,15.1,15.7,,,308745538,,PREVENT,ACCESS2,,,Health Insurance
+2015,US,United States,,US,BRFSS,Prevention,59,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,14.8,14.5,15.0,,,308745538,,PREVENT,ACCESS2,,,Health Insurance
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Arthritis among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,22.5,22.3,22.7,,,308745538,,HLTHOUT,ARTHRITIS,,,Arthritis
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,24.7,24.5,24.9,,,308745538,,HLTHOUT,ARTHRITIS,,,Arthritis
+2015,US,United States,,US,BRFSS,Unhealthy Behaviors,59,Binge drinking among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,17.2,16.9,17.4,,,308745538,,UNHBEH,BINGE,,,Binge Drinking
+2015,US,United States,,US,BRFSS,Unhealthy Behaviors,59,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,16.3,16.1,16.5,,,308745538,,UNHBEH,BINGE,,,Binge Drinking
+2015,US,United States,,US,BRFSS,Health Outcomes,59,High blood pressure among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,29.4,29.2,29.7,,,308745538,,HLTHOUT,BPHIGH,,,High Blood Pressure
+2015,US,United States,,US,BRFSS,Health Outcomes,59,High blood pressure among adults aged >=18 Years,%,CrdPrv,Crude prevalence,31.9,31.6,32.2,,,308745538,,HLTHOUT,BPHIGH,,,High Blood Pressure
+2015,US,United States,,US,BRFSS,Prevention,59,Taking medicine for high blood pressure control among adults aged >=18 Years with high blood pressure,%,AgeAdjPrv,Age-adjusted prevalence,57.7,57.1,58.4,,,308745538,,PREVENT,BPMED,,,Taking BP Medication
+2015,US,United States,,US,BRFSS,Prevention,59,Taking medicine for high blood pressure control among adults aged >=18 Years with high blood pressure,%,CrdPrv,Crude prevalence,77.2,76.8,77.7,,,308745538,,PREVENT,BPMED,,,Taking BP Medication
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Cancer (excluding skin cancer) among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,6.0,5.9,6.1,,,308745538,,HLTHOUT,CANCER,,,Cancer (except skin)
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Cancer (excluding skin cancer) among adults aged >=18 Years,%,CrdPrv,Crude prevalence,6.6,6.5,6.8,,,308745538,,HLTHOUT,CANCER,,,Cancer (except skin)
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Current asthma among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,8.7,8.6,8.9,,,308745538,,HLTHOUT,CASTHMA,,,Current Asthma
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Current asthma among adults aged >=18 Years,%,CrdPrv,Crude prevalence,8.8,8.6,9.0,,,308745538,,HLTHOUT,CASTHMA,,,Current Asthma
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Coronary heart disease among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,5.6,5.5,5.8,,,308745538,,HLTHOUT,CHD,,,Coronary Heart Disease
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Coronary heart disease among adults aged >=18 Years,%,CrdPrv,Crude prevalence,6.3,6.2,6.5,,,308745538,,HLTHOUT,CHD,,,Coronary Heart Disease
+2015,US,United States,,US,BRFSS,Prevention,59,Visits to doctor for routine checkup within the past Year among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,68.6,68.3,68.9,,,308745538,,PREVENT,CHECKUP,,,Annual Checkup
+2015,US,United States,,US,BRFSS,Prevention,59,Visits to doctor for routine checkup within the past Year among adults aged >=18 Years,%,CrdPrv,Crude prevalence,70.0,69.7,70.3,,,308745538,,PREVENT,CHECKUP,,,Annual Checkup
+2015,US,United States,,US,BRFSS,Prevention,59,Cholesterol screening among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,75.2,74.9,75.5,,,308745538,,PREVENT,CHOLSCREEN,,,Cholesterol Screening
+2015,US,United States,,US,BRFSS,Prevention,59,Cholesterol screening among adults aged >=18 Years,%,CrdPrv,Crude prevalence,77.0,76.7,77.3,,,308745538,,PREVENT,CHOLSCREEN,,,Cholesterol Screening
+2014,US,United States,,US,BRFSS,Prevention,59,"Fecal occult blood test, sigmoidoscopy, or colonoscopy among adults aged 50–75 Years",%,AgeAdjPrv,Age-adjusted prevalence,64.0,63.5,64.5,,,308745538,,PREVENT,COLON_SCREEN,,,Colorectal Cancer Screening
+2014,US,United States,,US,BRFSS,Prevention,59,"Fecal occult blood test, sigmoidoscopy, or colonoscopy among adults aged 50–75 Years",%,CrdPrv,Crude prevalence,63.7,63.3,64.1,,,308745538,,PREVENT,COLON_SCREEN,,,Colorectal Cancer Screening
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Chronic obstructive pulmonary disease among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,5.7,5.6,5.9,,,308745538,,HLTHOUT,COPD,,,COPD
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Chronic obstructive pulmonary disease among adults aged >=18 Years,%,CrdPrv,Crude prevalence,6.3,6.2,6.4,,,308745538,,HLTHOUT,COPD,,,COPD
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Physical health not good for >=14 days among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,11.5,11.3,11.7,,,308745538,,HLTHOUT,PHLTH,,,Physical Health
+2014,US,United States,,US,BRFSS,Prevention,59,"Older adult men aged >=65 Years who are up to date on a core set of clinical preventive services: Flu shot past Year, PPV shot ever, Colorectal cancer screening",%,AgeAdjPrv,Age-adjusted prevalence,32.9,32.1,33.6,,,308745538,,PREVENT,COREM,,,Core preventive services for older men
+2014,US,United States,,US,BRFSS,Prevention,59,"Older adult men aged >=65 Years who are up to date on a core set of clinical preventive services: Flu shot past Year, PPV shot ever, Colorectal cancer screening",%,CrdPrv,Crude prevalence,32.3,31.5,33.0,,,308745538,,PREVENT,COREM,,,Core preventive services for older men
+2014,US,United States,,US,BRFSS,Prevention,59,"Older adult women aged >=65 Years who are up to date on a core set of clinical preventive services: Flu shot past Year, PPV shot ever, Colorectal cancer screening, and Mammogram past 2 Years",%,AgeAdjPrv,Age-adjusted prevalence,30.7,30.2,31.4,,,308745538,,PREVENT,COREW,,,Core preventive services for older women
+2014,US,United States,,US,BRFSS,Prevention,59,"Older adult women aged >=65 Years who are up to date on a core set of clinical preventive services: Flu shot past Year, PPV shot ever, Colorectal cancer screening, and Mammogram past 2 Years",%,CrdPrv,Crude prevalence,30.7,30.1,31.3,,,308745538,,PREVENT,COREW,,,Core preventive services for older women
+2015,US,United States,,US,BRFSS,Unhealthy Behaviors,59,Current smoking among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,17.1,16.8,17.3,,,308745538,,UNHBEH,CSMOKING,,,Current Smoking
+2015,US,United States,,US,BRFSS,Unhealthy Behaviors,59,Current smoking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,16.8,16.6,17.0,,,308745538,,UNHBEH,CSMOKING,,,Current Smoking
+2014,US,United States,,US,BRFSS,Prevention,59,Visits to dentist or dental clinic among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,64.1,63.8,64.4,,,308745538,,PREVENT,DENTAL,,,Dental Visit
+2014,US,United States,,US,BRFSS,Prevention,59,Visits to dentist or dental clinic among adults aged >=18 Years,%,CrdPrv,Crude prevalence,64.4,64.1,64.7,,,308745538,,PREVENT,DENTAL,,,Dental Visit
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Diagnosed diabetes among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,9.3,9.2,9.5,,,308745538,,HLTHOUT,DIABETES,,,Diabetes
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Diagnosed diabetes among adults aged >=18 Years,%,CrdPrv,Crude prevalence,10.4,10.3,10.6,,,308745538,,HLTHOUT,DIABETES,,,Diabetes
+2015,US,United States,,US,BRFSS,Health Outcomes,59,High cholesterol among adults aged >=18 Years who have been screened in the past 5 Years,%,AgeAdjPrv,Age-adjusted prevalence,31.1,30.8,31.4,,,308745538,,HLTHOUT,HIGHCHOL,,,High Cholesterol
+2015,US,United States,,US,BRFSS,Health Outcomes,59,High cholesterol among adults aged >=18 Years who have been screened in the past 5 Years,%,CrdPrv,Crude prevalence,37.1,36.8,37.4,,,308745538,,HLTHOUT,HIGHCHOL,,,High Cholesterol
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Chronic kidney disease among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,2.5,2.4,2.6,,,308745538,,HLTHOUT,KIDNEY,,,Chronic Kidney Disease
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Chronic kidney disease among adults aged >=18 Years,%,CrdPrv,Crude prevalence,2.7,2.6,2.8,,,308745538,,HLTHOUT,KIDNEY,,,Chronic Kidney Disease
+2015,US,United States,,US,BRFSS,Unhealthy Behaviors,59,No leisure-time physical activity among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,25.5,25.2,25.8,,,308745538,,UNHBEH,LPA,,,Physical Inactivity
+2015,US,United States,,US,BRFSS,Unhealthy Behaviors,59,No leisure-time physical activity among adults aged >=18 Years,%,CrdPrv,Crude prevalence,25.9,25.6,26.1,,,308745538,,UNHBEH,LPA,,,Physical Inactivity
+2014,US,United States,,US,BRFSS,Prevention,59,Mammography use among women aged 50–74 Years,%,AgeAdjPrv,Age-adjusted prevalence,75.5,75.1,75.9,,,308745538,,PREVENT,MAMMOUSE,,,Mammography
+2014,US,United States,,US,BRFSS,Prevention,59,Mammography use among women aged 50–74 Years,%,CrdPrv,Crude prevalence,75.8,75.4,76.2,,,308745538,,PREVENT,MAMMOUSE,,,Mammography
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Mental health not good for >=14 days among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,11.6,11.4,11.8,,,308745538,,HLTHOUT,MHLTH,,,Mental Health
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Mental health not good for >=14 days among adults aged >=18 Years,%,CrdPrv,Crude prevalence,11.4,11.3,11.6,,,308745538,,HLTHOUT,MHLTH,,,Mental Health
+2015,US,United States,,US,BRFSS,Unhealthy Behaviors,59,Obesity among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,28.7,28.4,29.0,,,308745538,,UNHBEH,OBESITY,,,Obesity
+2015,US,United States,,US,BRFSS,Unhealthy Behaviors,59,Obesity among adults aged >=18 Years,%,CrdPrv,Crude prevalence,28.8,28.6,29.1,,,308745538,,UNHBEH,OBESITY,,,Obesity
+2014,US,United States,,US,BRFSS,Prevention,59,Papanicolaou smear use among adult women aged 21–65 Years,%,AgeAdjPrv,Age-adjusted prevalence,81.1,80.6,81.6,,,308745538,,PREVENT,PAPTEST,,,Pap Smear Test
+2014,US,United States,,US,BRFSS,Prevention,59,Papanicolaou smear use among adult women aged 21–65 Years,%,CrdPrv,Crude prevalence,81.8,81.3,82.2,,,308745538,,PREVENT,PAPTEST,,,Pap Smear Test
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Physical health not good for >=14 days among adults aged >=18 Years,%,CrdPrv,Crude prevalence,12.0,11.8,12.2,,,308745538,,HLTHOUT,PHLTH,,,Physical Health
+2014,US,United States,,US,BRFSS,Unhealthy Behaviors,59,Sleeping less than 7 hours among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,35.1,34.8,35.5,,,308745538,,UNHBEH,SLEEP,,,Sleep < 7 hours
+2014,US,United States,,US,BRFSS,Unhealthy Behaviors,59,Sleeping less than 7 hours among adults aged >=18 Years,%,CrdPrv,Crude prevalence,34.8,34.5,35.1,,,308745538,,UNHBEH,SLEEP,,,Sleep < 7 hours
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Stroke among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,2.8,2.7,2.8,,,308745538,,HLTHOUT,STROKE,,,Stroke
+2015,US,United States,,US,BRFSS,Health Outcomes,59,Stroke among adults aged >=18 Years,%,CrdPrv,Crude prevalence,3.0,3.0,3.1,,,308745538,,HLTHOUT,STROKE,,,Stroke
+2014,US,United States,,US,BRFSS,Health Outcomes,59,All teeth lost among adults aged >=65 Years,%,AgeAdjPrv,Age-adjusted prevalence,15.4,15.0,15.8,,,308745538,,HLTHOUT,TEETHLOST,,,Teeth Loss
+2014,US,United States,,US,BRFSS,Health Outcomes,59,All teeth lost among adults aged >=65 Years,%,CrdPrv,Crude prevalence,14.9,14.6,15.3,,,308745538,,HLTHOUT,TEETHLOST,,,Teeth Loss
+2015,AL,Alabama,Birmingham,City,BRFSS,Prevention,0107000,Current lack of health insurance among adults aged 18–64 Years,%,AgeAdjPrv,Age-adjusted prevalence,19.8,19.5,20.2,,,212237,"(33.5275663773, -86.7988174678)",PREVENT,ACCESS2,0107000,,Health Insurance
+2015,AL,Alabama,Birmingham,City,BRFSS,Prevention,0107000,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,19.6,19.2,20.0,,,212237,"(33.5275663773, -86.7988174678)",PREVENT,ACCESS2,0107000,,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073000100,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,23.9,21.2,27.2,,,3042,"(33.5794328326, -86.7228323926)",PREVENT,ACCESS2,0107000,01073000100,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073000300,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,28.8,25.4,32.4,,,2735,"(33.5428208686, -86.752433978)",PREVENT,ACCESS2,0107000,01073000300,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073000400,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,26.1,22.6,29.9,,,3338,"(33.5632449633, -86.7640474064)",PREVENT,ACCESS2,0107000,01073000400,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073000500,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,28.1,24.6,32.0,,,2864,"(33.5442404594, -86.7749130719)",PREVENT,ACCESS2,0107000,01073000500,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073000700,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,31.8,27.0,36.7,,,2577,"(33.5525406139, -86.8016893706)",PREVENT,ACCESS2,0107000,01073000700,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073000800,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,22.4,19.1,26.1,,,3859,"(33.549697789, -86.8330944744)",PREVENT,ACCESS2,0107000,01073000800,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073001100,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,16.8,13.7,20.5,,,5354,"(33.5429143325, -86.8756782852)",PREVENT,ACCESS2,0107000,01073001100,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073001200,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,24.6,22.2,27.2,,,2876,"(33.5278767706, -86.8604161686)",PREVENT,ACCESS2,0107000,01073001200,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073001400,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,22.0,18.4,25.7,,,2181,"(33.5261497258, -86.835146606)",PREVENT,ACCESS2,0107000,01073001400,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073001500,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,26.3,23.1,29.4,,,3189,"(33.5298727342, -86.8197191685)",PREVENT,ACCESS2,0107000,01073001500,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073001600,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,26.8,22.9,30.8,,,3390,"(33.5372993423, -86.8036590482)",PREVENT,ACCESS2,0107000,01073001600,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073001902,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,26.9,24.1,29.8,,,1894,"(33.5532050997, -86.7429801603)",PREVENT,ACCESS2,0107000,01073001902,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073002000,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,24.4,20.8,28.2,,,3885,"(33.5541574106, -86.7167229915)",PREVENT,ACCESS2,0107000,01073002000,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073002100,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,21.5,18.1,25.0,,,3186,"(33.5650015942, -86.7101024766)",PREVENT,ACCESS2,0107000,01073002100,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073002200,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,21.7,18.7,25.0,,,2630,"(33.5521301205, -86.7276759508)",PREVENT,ACCESS2,0107000,01073002200,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073002303,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,27.0,23.7,30.7,,,2936,"(33.5383153207, -86.7270445428)",PREVENT,ACCESS2,0107000,01073002303,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073002305,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,9.9,8.2,12.1,,,2952,"(33.5333415976, -86.7479566084)",PREVENT,ACCESS2,0107000,01073002305,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073002306,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,9.5,8.1,11.2,,,3257,"(33.5213873564, -86.7490031289)",PREVENT,ACCESS2,0107000,01073002306,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073002400,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,25.1,22.4,27.8,,,3629,"(33.5260748309, -86.7830315488)",PREVENT,ACCESS2,0107000,01073002400,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073002700,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,20.7,16.8,24.9,,,3992,"(33.5176008419, -86.8106887452)",PREVENT,ACCESS2,0107000,01073002700,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073002900,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,25.7,21.5,29.9,,,2064,"(33.5132498864, -86.83004749)",PREVENT,ACCESS2,0107000,01073002900,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003001,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,18.4,15.3,22.4,,,3779,"(33.5125158094, -86.8577164946)",PREVENT,ACCESS2,0107000,01073003001,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003002,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,26.3,21.7,31.3,,,2203,"(33.512258109, -86.8441439907)",PREVENT,ACCESS2,0107000,01073003002,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003100,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,23.2,20.2,26.7,,,3637,"(33.5059655756, -86.8745506086)",PREVENT,ACCESS2,0107000,01073003100,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003200,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,28.7,25.0,32.8,,,931,"(33.5094018502, -86.8859081961)",PREVENT,ACCESS2,0107000,01073003200,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003300,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,22.2,19.1,25.7,,,947,"(33.5171261108, -86.8913819749)",PREVENT,ACCESS2,0107000,01073003300,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003400,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,26.8,23.0,30.9,,,2477,"(33.5052229234, -86.9014844656)",PREVENT,ACCESS2,0107000,01073003400,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003500,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,22.3,19.1,25.7,,,2780,"(33.5065714011, -86.9195910063)",PREVENT,ACCESS2,0107000,01073003500,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003600,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,17.0,14.5,19.6,,,4683,"(33.48476397, -86.8981392947)",PREVENT,ACCESS2,0107000,01073003600,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003700,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,21.6,19.2,23.9,,,5063,"(33.4969018589, -86.8907729426)",PREVENT,ACCESS2,0107000,01073003700,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003802,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,19.0,16.7,21.6,,,5409,"(33.4785707794, -86.890000907)",PREVENT,ACCESS2,0107000,01073003802,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003803,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,20.9,17.7,24.2,,,4199,"(33.485945214, -86.869692186)",PREVENT,ACCESS2,0107000,01073003803,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073003900,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,29.9,26.9,33.0,,,1783,"(33.4989959327, -86.8647600038)",PREVENT,ACCESS2,0107000,01073003900,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073004000,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,25.8,21.4,30.3,,,3772,"(33.4953246015, -86.8516232073)",PREVENT,ACCESS2,0107000,01073004000,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073004200,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,23.4,20.8,26.1,,,2341,"(33.5007439361, -86.8270720379)",PREVENT,ACCESS2,0107000,01073004200,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073004500,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,22.7,19.3,27.4,,,5003,"(33.5041857556, -86.8033798346)",PREVENT,ACCESS2,0107000,01073004500,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073004701,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,10.6,8.6,13.4,,,3480,"(33.5075242148, -86.7836675838)",PREVENT,ACCESS2,0107000,01073004701,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073004702,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,7.1,6.1,8.7,,,2944,"(33.5119902661, -86.7694550989)",PREVENT,ACCESS2,0107000,01073004702,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073004800,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,8.7,7.3,10.3,,,1861,"(33.4989064008, -86.78269914)",PREVENT,ACCESS2,0107000,01073004800,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073004901,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,11.0,9.1,13.2,,,1167,"(33.4971595645, -86.7917440668)",PREVENT,ACCESS2,0107000,01073004901,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073004902,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,15.2,12.5,18.5,,,3146,"(33.4935824043, -86.8009294603)",PREVENT,ACCESS2,0107000,01073004902,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005000,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,17.9,15.4,20.7,,,3482,"(33.4866689795, -86.8173262831)",PREVENT,ACCESS2,0107000,01073005000,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005101,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,30.5,26.5,34.4,,,1507,"(33.4945909008, -86.834763936)",PREVENT,ACCESS2,0107000,01073005101,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005103,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,17.5,14.4,20.7,,,2587,"(33.485714885, -86.8327817467)",PREVENT,ACCESS2,0107000,01073005103,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005104,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,24.9,21.3,28.9,,,2881,"(33.4749941816, -86.8335421747)",PREVENT,ACCESS2,0107000,01073005104,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005200,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,20.2,16.4,24.2,,,3740,"(33.4806708775, -86.8508671514)",PREVENT,ACCESS2,0107000,01073005200,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005302,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,12.8,10.9,15.1,,,3463,"(33.5766456449, -86.6965590316)",PREVENT,ACCESS2,0107000,01073005302,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005500,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,26.5,22.9,30.7,,,1824,"(33.5670293898, -86.8005567213)",PREVENT,ACCESS2,0107000,01073005500,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005600,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,12.3,10.0,15.0,,,4367,"(33.5200981234, -86.7272063198)",PREVENT,ACCESS2,0107000,01073005600,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005701,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,18.9,15.7,22.3,,,2372,"(33.4629543329, -86.8898406628)",PREVENT,ACCESS2,0107000,01073005701,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005702,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,19.8,16.4,23.3,,,3413,"(33.4698691385, -86.874290602)",PREVENT,ACCESS2,0107000,01073005702,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005800,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,19.3,16.4,22.3,,,4216,"(33.479062325, -86.8128063089)",PREVENT,ACCESS2,0107000,01073005800,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005903,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,15.9,13.9,18.0,,,4933,"(33.597162309, -86.6766736351)",PREVENT,ACCESS2,0107000,01073005903,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005905,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,21.4,18.2,24.7,,,5039,"(33.603988456, -86.7008123418)",PREVENT,ACCESS2,0107000,01073005905,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005907,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,12.5,10.3,14.9,,,1975,"(33.6142861501, -86.6691996417)",PREVENT,ACCESS2,0107000,01073005907,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005908,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,20.9,18.1,23.7,,,1621,"(33.6182924432, -86.6801483084)",PREVENT,ACCESS2,0107000,01073005908,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005909,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,14.4,11.8,17.5,,,2524,"(33.611811773, -86.7214221514)",PREVENT,ACCESS2,0107000,01073005909,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073005910,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,14.7,12.6,17.1,,,4612,"(33.6299017499, -86.7194311229)",PREVENT,ACCESS2,0107000,01073005910,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073010500,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,21.8,18.2,25.9,,,114,"(33.4363786806, -86.9128923072)",PREVENT,ACCESS2,0107000,01073010500,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073010701,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,18.5,14.4,23.6,,,74,"(33.473886155, -86.8146487762)",PREVENT,ACCESS2,0107000,01073010701,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073010706,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,13.5,11.0,16.3,,,1528,"(33.4443709442, -86.8405352645)",PREVENT,ACCESS2,0107000,01073010706,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073010801,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,5.2,4.4,6.3,,,168,"(33.514097853, -86.7466971362)",PREVENT,ACCESS2,0107000,01073010801,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073010802,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,5.0,3.9,6.5,,,172,"(33.4885493477, -86.780843024)",PREVENT,ACCESS2,0107000,01073010802,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073010803,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,11.3,9.0,14.0,,,514,"(33.5229093892, -86.7102618642)",PREVENT,ACCESS2,0107000,01073010803,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073010805,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,7.3,5.5,9.9,,,86,"(33.4952792472, -86.6987184974)",PREVENT,ACCESS2,0107000,01073010805,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073011104,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,13.6,11.4,16.1,,,1688,"(33.6159436433, -86.6557892507)",PREVENT,ACCESS2,0107000,01073011104,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073011107,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,42,"(33.5804845249, -86.6301110961)",PREVENT,ACCESS2,0107000,01073011107,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073011108,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,9,"(33.6050742596, -86.6316729386)",PREVENT,ACCESS2,0107000,01073011108,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073011207,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,17.7,15.1,20.6,,,815,"(33.6718848706, -86.6772510465)",PREVENT,ACCESS2,0107000,01073011207,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073011209,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,20.2,17.2,24.2,,,1062,"(33.6557189992, -86.7050698349)",PREVENT,ACCESS2,0107000,01073011209,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073011210,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,21.5,17.0,26.7,,,1385,"(33.6641893755, -86.6956170686)",PREVENT,ACCESS2,0107000,01073011210,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073011803,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,18.1,15.4,21.3,,,928,"(33.6252575173, -86.6998610409)",PREVENT,ACCESS2,0107000,01073011803,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073011804,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,18.3,15.2,21.9,,,1157,"(33.6474916175, -86.7042974424)",PREVENT,ACCESS2,0107000,01073011804,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073011901,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,6,"(33.6355414646, -86.736946691)",PREVENT,ACCESS2,0107000,01073011901,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073011904,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,14.4,12.1,16.8,,,1915,"(33.593140185, -86.7357930541)",PREVENT,ACCESS2,0107000,01073011904,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012001,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,14.5,12.5,16.7,,,304,"(33.5919486112, -86.864384068)",PREVENT,ACCESS2,0107000,01073012001,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012002,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,44,"(33.5859234197, -86.8357007188)",PREVENT,ACCESS2,0107000,01073012002,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012200,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,23,"(33.5967441904, -87.0879396857)",PREVENT,ACCESS2,0107000,01073012200,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012302,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,12.7,10.6,15.2,,,144,"(33.5542816352, -87.0544691416)",PREVENT,ACCESS2,0107000,01073012302,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012305,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,12.5,10.2,15.1,,,403,"(33.4695358064, -86.96831739)",PREVENT,ACCESS2,0107000,01073012305,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012401,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,11.4,8.9,14.2,,,1066,"(33.5571951048, -86.8777935049)",PREVENT,ACCESS2,0107000,01073012401,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012402,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,16.7,14.6,19.0,,,418,"(33.549984172, -86.8994543327)",PREVENT,ACCESS2,0107000,01073012402,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012500,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,21.0,17.6,24.4,,,410,"(33.529160486, -86.9346476445)",PREVENT,ACCESS2,0107000,01073012500,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012602,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,17.5,15.7,19.6,,,371,"(33.570164674, -86.666430582)",PREVENT,ACCESS2,0107000,01073012602,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012701,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,44,"(33.5484078071, -86.6323773455)",PREVENT,ACCESS2,0107000,01073012701,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012703,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,11.3,8.6,14.9,,,498,"(33.4681180943, -86.6671888213)",PREVENT,ACCESS2,0107000,01073012703,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012704,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,8.9,6.8,11.5,,,113,"(33.5034195908, -86.6180983403)",PREVENT,ACCESS2,0107000,01073012704,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012803,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,8.6,6.7,11.2,,,1261,"(33.4439425865, -86.7212936938)",PREVENT,ACCESS2,0107000,01073012803,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073012910,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,9,"(33.4345805042, -86.7263292059)",PREVENT,ACCESS2,0107000,01073012910,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073013002,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,21.3,17.1,25.6,,,1514,"(33.46604181, -86.8567287797)",PREVENT,ACCESS2,0107000,01073013002,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073013100,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,21.8,18.8,24.9,,,4424,"(33.44880214, -86.8878401579)",PREVENT,ACCESS2,0107000,01073013100,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073013300,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,23.5,19.5,27.5,,,1782,"(33.4396443569, -86.9248768665)",PREVENT,ACCESS2,0107000,01073013300,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073013901,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,18.0,14.8,21.7,,,952,"(33.4729384906, -86.9547337648)",PREVENT,ACCESS2,0107000,01073013901,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073014302,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,10.0,8.3,12.1,,,2778,"(33.4244658829, -86.8841474217)",PREVENT,ACCESS2,0107000,01073014302,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01073014413,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,7.6,5.9,9.8,,,397,"(33.4226593117, -86.8508620751)",PREVENT,ACCESS2,0107000,01073014413,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01117030213,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,9.3,7.7,11.4,,,644,"(33.4395975193, -86.6735959359)",PREVENT,ACCESS2,0107000,01117030213,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01117030217,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,16,"(33.4556995763, -86.6520208639)",PREVENT,ACCESS2,0107000,01117030217,Health Insurance
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Prevention,0107000-01117030303,Current lack of health insurance among adults aged 18–64 Years,%,CrdPrv,Crude prevalence,9.9,8.1,12.0,,,968,"(33.4258661239, -86.713819356)",PREVENT,ACCESS2,0107000,01117030303,Health Insurance
+2015,AL,Alabama,Birmingham,City,BRFSS,Health Outcomes,0107000,Arthritis among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,31.0,30.8,31.1,,,212237,"(33.5275663773, -86.7988174678)",HLTHOUT,ARTHRITIS,0107000,,Arthritis
+2015,AL,Alabama,Birmingham,City,BRFSS,Health Outcomes,0107000,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,30.9,30.8,31.1,,,212237,"(33.5275663773, -86.7988174678)",HLTHOUT,ARTHRITIS,0107000,,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073000100,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,32.5,31.5,33.6,,,3042,"(33.5794328326, -86.7228323926)",HLTHOUT,ARTHRITIS,0107000,01073000100,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073000300,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,31.3,30.0,32.4,,,2735,"(33.5428208686, -86.752433978)",HLTHOUT,ARTHRITIS,0107000,01073000300,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073000400,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,34.6,33.2,35.9,,,3338,"(33.5632449633, -86.7640474064)",HLTHOUT,ARTHRITIS,0107000,01073000400,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073000500,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,37.8,36.3,39.2,,,2864,"(33.5442404594, -86.7749130719)",HLTHOUT,ARTHRITIS,0107000,01073000500,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073000700,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,38.5,37.1,39.9,,,2577,"(33.5525406139, -86.8016893706)",HLTHOUT,ARTHRITIS,0107000,01073000700,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073000800,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,38.0,36.5,39.3,,,3859,"(33.549697789, -86.8330944744)",HLTHOUT,ARTHRITIS,0107000,01073000800,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073001100,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,34.0,32.4,35.5,,,5354,"(33.5429143325, -86.8756782852)",HLTHOUT,ARTHRITIS,0107000,01073001100,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073001200,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,36.5,35.4,37.6,,,2876,"(33.5278767706, -86.8604161686)",HLTHOUT,ARTHRITIS,0107000,01073001200,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073001400,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,37.1,35.6,38.6,,,2181,"(33.5261497258, -86.835146606)",HLTHOUT,ARTHRITIS,0107000,01073001400,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073001500,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,35.2,34.0,36.4,,,3189,"(33.5298727342, -86.8197191685)",HLTHOUT,ARTHRITIS,0107000,01073001500,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073001600,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,39.9,38.4,41.3,,,3390,"(33.5372993423, -86.8036590482)",HLTHOUT,ARTHRITIS,0107000,01073001600,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073001902,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,35.1,34.0,36.1,,,1894,"(33.5532050997, -86.7429801603)",HLTHOUT,ARTHRITIS,0107000,01073001902,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073002000,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,36.3,34.9,37.7,,,3885,"(33.5541574106, -86.7167229915)",HLTHOUT,ARTHRITIS,0107000,01073002000,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073002100,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,33.1,31.8,34.3,,,3186,"(33.5650015942, -86.7101024766)",HLTHOUT,ARTHRITIS,0107000,01073002100,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073002200,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,32.4,31.2,33.6,,,2630,"(33.5521301205, -86.7276759508)",HLTHOUT,ARTHRITIS,0107000,01073002200,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073002303,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,33.4,32.2,34.6,,,2936,"(33.5383153207, -86.7270445428)",HLTHOUT,ARTHRITIS,0107000,01073002303,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073002305,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,22.3,21.3,23.3,,,2952,"(33.5333415976, -86.7479566084)",HLTHOUT,ARTHRITIS,0107000,01073002305,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073002306,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,26.9,25.9,27.9,,,3257,"(33.5213873564, -86.7490031289)",HLTHOUT,ARTHRITIS,0107000,01073002306,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073002400,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,31.0,30.0,31.9,,,3629,"(33.5260748309, -86.7830315488)",HLTHOUT,ARTHRITIS,0107000,01073002400,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073002700,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,27.2,25.9,28.5,,,3992,"(33.5176008419, -86.8106887452)",HLTHOUT,ARTHRITIS,0107000,01073002700,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073002900,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,38.7,37.0,40.3,,,2064,"(33.5132498864, -86.83004749)",HLTHOUT,ARTHRITIS,0107000,01073002900,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003001,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,24.3,23.6,25.0,,,3779,"(33.5125158094, -86.8577164946)",HLTHOUT,ARTHRITIS,0107000,01073003001,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003002,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,40.9,39.1,42.7,,,2203,"(33.512258109, -86.8441439907)",HLTHOUT,ARTHRITIS,0107000,01073003002,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003100,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,34.1,32.7,35.4,,,3637,"(33.5059655756, -86.8745506086)",HLTHOUT,ARTHRITIS,0107000,01073003100,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003200,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,39.0,37.6,40.3,,,931,"(33.5094018502, -86.8859081961)",HLTHOUT,ARTHRITIS,0107000,01073003200,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003300,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,38.6,37.3,39.9,,,947,"(33.5171261108, -86.8913819749)",HLTHOUT,ARTHRITIS,0107000,01073003300,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003400,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,36.3,34.9,37.7,,,2477,"(33.5052229234, -86.9014844656)",HLTHOUT,ARTHRITIS,0107000,01073003400,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003500,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,32.8,31.5,34.0,,,2780,"(33.5065714011, -86.9195910063)",HLTHOUT,ARTHRITIS,0107000,01073003500,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003600,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,33.4,32.1,34.7,,,4683,"(33.48476397, -86.8981392947)",HLTHOUT,ARTHRITIS,0107000,01073003600,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003700,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,31.2,30.1,32.1,,,5063,"(33.4969018589, -86.8907729426)",HLTHOUT,ARTHRITIS,0107000,01073003700,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003802,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,32.1,31.0,33.2,,,5409,"(33.4785707794, -86.890000907)",HLTHOUT,ARTHRITIS,0107000,01073003802,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003803,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,36.0,34.5,37.3,,,4199,"(33.485945214, -86.869692186)",HLTHOUT,ARTHRITIS,0107000,01073003803,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073003900,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,32.9,31.8,33.9,,,1783,"(33.4989959327, -86.8647600038)",HLTHOUT,ARTHRITIS,0107000,01073003900,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073004000,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,37.8,36.4,39.3,,,3772,"(33.4953246015, -86.8516232073)",HLTHOUT,ARTHRITIS,0107000,01073004000,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073004200,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,36.6,35.5,37.7,,,2341,"(33.5007439361, -86.8270720379)",HLTHOUT,ARTHRITIS,0107000,01073004200,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073004500,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,15.1,14.4,15.8,,,5003,"(33.5041857556, -86.8033798346)",HLTHOUT,ARTHRITIS,0107000,01073004500,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073004701,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,23.7,22.5,24.9,,,3480,"(33.5075242148, -86.7836675838)",HLTHOUT,ARTHRITIS,0107000,01073004701,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073004702,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,27.0,25.9,28.1,,,2944,"(33.5119902661, -86.7694550989)",HLTHOUT,ARTHRITIS,0107000,01073004702,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073004800,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,30.1,29.0,31.2,,,1861,"(33.4989064008, -86.78269914)",HLTHOUT,ARTHRITIS,0107000,01073004800,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073004901,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,22.1,21.2,23.0,,,1167,"(33.4971595645, -86.7917440668)",HLTHOUT,ARTHRITIS,0107000,01073004901,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073004902,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,18.4,17.5,19.4,,,3146,"(33.4935824043, -86.8009294603)",HLTHOUT,ARTHRITIS,0107000,01073004902,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005000,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,19.6,18.7,20.4,,,3482,"(33.4866689795, -86.8173262831)",HLTHOUT,ARTHRITIS,0107000,01073005000,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005101,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,36.6,35.3,37.8,,,1507,"(33.4945909008, -86.834763936)",HLTHOUT,ARTHRITIS,0107000,01073005101,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005103,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,40.1,38.6,41.7,,,2587,"(33.485714885, -86.8327817467)",HLTHOUT,ARTHRITIS,0107000,01073005103,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005104,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,18.9,18.0,19.8,,,2881,"(33.4749941816, -86.8335421747)",HLTHOUT,ARTHRITIS,0107000,01073005104,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005200,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,37.9,36.1,39.7,,,3740,"(33.4806708775, -86.8508671514)",HLTHOUT,ARTHRITIS,0107000,01073005200,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005302,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,33.2,31.8,34.5,,,3463,"(33.5766456449, -86.6965590316)",HLTHOUT,ARTHRITIS,0107000,01073005302,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005500,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,36.7,35.4,37.9,,,1824,"(33.5670293898, -86.8005567213)",HLTHOUT,ARTHRITIS,0107000,01073005500,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005600,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,31.8,30.3,33.2,,,4367,"(33.5200981234, -86.7272063198)",HLTHOUT,ARTHRITIS,0107000,01073005600,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005701,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,35.8,34.1,37.3,,,2372,"(33.4629543329, -86.8898406628)",HLTHOUT,ARTHRITIS,0107000,01073005701,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005702,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,37.8,36.0,39.3,,,3413,"(33.4698691385, -86.874290602)",HLTHOUT,ARTHRITIS,0107000,01073005702,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005800,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,18.4,17.7,19.1,,,4216,"(33.479062325, -86.8128063089)",HLTHOUT,ARTHRITIS,0107000,01073005800,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005903,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,32.2,31.1,33.3,,,4933,"(33.597162309, -86.6766736351)",HLTHOUT,ARTHRITIS,0107000,01073005903,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005905,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,34.2,32.9,35.4,,,5039,"(33.603988456, -86.7008123418)",HLTHOUT,ARTHRITIS,0107000,01073005905,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005907,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,30.1,28.9,31.3,,,1975,"(33.6142861501, -86.6691996417)",HLTHOUT,ARTHRITIS,0107000,01073005907,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005908,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,32.5,31.5,33.5,,,1621,"(33.6182924432, -86.6801483084)",HLTHOUT,ARTHRITIS,0107000,01073005908,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005909,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,28.0,26.5,29.4,,,2524,"(33.611811773, -86.7214221514)",HLTHOUT,ARTHRITIS,0107000,01073005909,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073005910,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,28.0,26.8,29.1,,,4612,"(33.6299017499, -86.7194311229)",HLTHOUT,ARTHRITIS,0107000,01073005910,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073010500,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,41.3,39.4,43.1,,,114,"(33.4363786806, -86.9128923072)",HLTHOUT,ARTHRITIS,0107000,01073010500,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073010701,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,15.1,14.0,16.3,,,74,"(33.473886155, -86.8146487762)",HLTHOUT,ARTHRITIS,0107000,01073010701,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073010706,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,15.3,14.6,16.0,,,1528,"(33.4443709442, -86.8405352645)",HLTHOUT,ARTHRITIS,0107000,01073010706,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073010801,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,24.9,23.9,26.2,,,168,"(33.514097853, -86.7466971362)",HLTHOUT,ARTHRITIS,0107000,01073010801,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073010802,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,33.2,31.7,35.0,,,172,"(33.4885493477, -86.780843024)",HLTHOUT,ARTHRITIS,0107000,01073010802,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073010803,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,23.8,22.6,25.0,,,514,"(33.5229093892, -86.7102618642)",HLTHOUT,ARTHRITIS,0107000,01073010803,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073010805,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,32.0,30.1,34.0,,,86,"(33.4952792472, -86.6987184974)",HLTHOUT,ARTHRITIS,0107000,01073010805,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073011104,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,29.8,28.5,31.1,,,1688,"(33.6159436433, -86.6557892507)",HLTHOUT,ARTHRITIS,0107000,01073011104,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073011107,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,42,"(33.5804845249, -86.6301110961)",HLTHOUT,ARTHRITIS,0107000,01073011107,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073011108,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,9,"(33.6050742596, -86.6316729386)",HLTHOUT,ARTHRITIS,0107000,01073011108,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073011207,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,25.3,24.3,26.2,,,815,"(33.6718848706, -86.6772510465)",HLTHOUT,ARTHRITIS,0107000,01073011207,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073011209,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,27.2,26.1,28.3,,,1062,"(33.6557189992, -86.7050698349)",HLTHOUT,ARTHRITIS,0107000,01073011209,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073011210,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,23.8,22.3,25.3,,,1385,"(33.6641893755, -86.6956170686)",HLTHOUT,ARTHRITIS,0107000,01073011210,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073011803,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,28.2,27.0,29.4,,,928,"(33.6252575173, -86.6998610409)",HLTHOUT,ARTHRITIS,0107000,01073011803,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073011804,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,25.8,24.6,27.0,,,1157,"(33.6474916175, -86.7042974424)",HLTHOUT,ARTHRITIS,0107000,01073011804,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073011901,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,6,"(33.6355414646, -86.736946691)",HLTHOUT,ARTHRITIS,0107000,01073011901,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073011904,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,34.8,33.3,36.3,,,1915,"(33.593140185, -86.7357930541)",HLTHOUT,ARTHRITIS,0107000,01073011904,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012001,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,44.8,42.9,46.6,,,304,"(33.5919486112, -86.864384068)",HLTHOUT,ARTHRITIS,0107000,01073012001,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012002,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,44,"(33.5859234197, -86.8357007188)",HLTHOUT,ARTHRITIS,0107000,01073012002,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012200,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,23,"(33.5967441904, -87.0879396857)",HLTHOUT,ARTHRITIS,0107000,01073012200,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012302,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,26.3,25.2,27.5,,,144,"(33.5542816352, -87.0544691416)",HLTHOUT,ARTHRITIS,0107000,01073012302,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012305,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,30.5,28.6,32.2,,,403,"(33.4695358064, -86.96831739)",HLTHOUT,ARTHRITIS,0107000,01073012305,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012401,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,27.4,25.6,29.4,,,1066,"(33.5571951048, -86.8777935049)",HLTHOUT,ARTHRITIS,0107000,01073012401,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012402,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,31.9,30.8,33.0,,,418,"(33.549984172, -86.8994543327)",HLTHOUT,ARTHRITIS,0107000,01073012402,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012500,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,37.9,36.5,39.4,,,410,"(33.529160486, -86.9346476445)",HLTHOUT,ARTHRITIS,0107000,01073012500,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012602,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,35.4,34.5,36.3,,,371,"(33.570164674, -86.666430582)",HLTHOUT,ARTHRITIS,0107000,01073012602,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012701,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,44,"(33.5484078071, -86.6323773455)",HLTHOUT,ARTHRITIS,0107000,01073012701,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012703,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,14.8,13.8,15.9,,,498,"(33.4681180943, -86.6671888213)",HLTHOUT,ARTHRITIS,0107000,01073012703,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012704,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,27.0,25.2,29.0,,,113,"(33.5034195908, -86.6180983403)",HLTHOUT,ARTHRITIS,0107000,01073012704,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012803,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,11.9,11.1,12.7,,,1261,"(33.4439425865, -86.7212936938)",HLTHOUT,ARTHRITIS,0107000,01073012803,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073012910,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,9,"(33.4345805042, -86.7263292059)",HLTHOUT,ARTHRITIS,0107000,01073012910,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073013002,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,41.0,39.1,42.9,,,1514,"(33.46604181, -86.8567287797)",HLTHOUT,ARTHRITIS,0107000,01073013002,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073013100,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,38.7,37.5,39.9,,,4424,"(33.44880214, -86.8878401579)",HLTHOUT,ARTHRITIS,0107000,01073013100,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073013300,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,39.2,37.6,40.7,,,1782,"(33.4396443569, -86.9248768665)",HLTHOUT,ARTHRITIS,0107000,01073013300,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073013901,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,35.0,33.4,36.6,,,952,"(33.4729384906, -86.9547337648)",HLTHOUT,ARTHRITIS,0107000,01073013901,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073014302,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,19.0,18.1,19.8,,,2778,"(33.4244658829, -86.8841474217)",HLTHOUT,ARTHRITIS,0107000,01073014302,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01073014413,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,17.7,16.9,18.6,,,397,"(33.4226593117, -86.8508620751)",HLTHOUT,ARTHRITIS,0107000,01073014413,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01117030213,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,20.3,19.1,21.5,,,644,"(33.4395975193, -86.6735959359)",HLTHOUT,ARTHRITIS,0107000,01117030213,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01117030217,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,,,,*,Estimates suppressed for population less than 50,16,"(33.4556995763, -86.6520208639)",HLTHOUT,ARTHRITIS,0107000,01117030217,Arthritis
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Health Outcomes,0107000-01117030303,Arthritis among adults aged >=18 Years,%,CrdPrv,Crude prevalence,12.8,12.1,13.6,,,968,"(33.4258661239, -86.713819356)",HLTHOUT,ARTHRITIS,0107000,01117030303,Arthritis
+2015,AL,Alabama,Birmingham,City,BRFSS,Unhealthy Behaviors,0107000,Binge drinking among adults aged >=18 Years,%,AgeAdjPrv,Age-adjusted prevalence,11.2,11.1,11.3,,,212237,"(33.5275663773, -86.7988174678)",UNHBEH,BINGE,0107000,,Binge Drinking
+2015,AL,Alabama,Birmingham,City,BRFSS,Unhealthy Behaviors,0107000,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,11.3,11.3,11.4,,,212237,"(33.5275663773, -86.7988174678)",UNHBEH,BINGE,0107000,,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073000100,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,10.1,9.7,10.5,,,3042,"(33.5794328326, -86.7228323926)",UNHBEH,BINGE,0107000,01073000100,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073000300,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,10.8,10.3,11.2,,,2735,"(33.5428208686, -86.752433978)",UNHBEH,BINGE,0107000,01073000300,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073000400,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.5,9.0,10.0,,,3338,"(33.5632449633, -86.7640474064)",UNHBEH,BINGE,0107000,01073000400,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073000500,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,8.6,8.1,9.0,,,2864,"(33.5442404594, -86.7749130719)",UNHBEH,BINGE,0107000,01073000500,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073000700,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,7.4,6.9,7.9,,,2577,"(33.5525406139, -86.8016893706)",UNHBEH,BINGE,0107000,01073000700,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073000800,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,8.9,8.5,9.3,,,3859,"(33.549697789, -86.8330944744)",UNHBEH,BINGE,0107000,01073000800,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073001100,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.6,9.2,10.1,,,5354,"(33.5429143325, -86.8756782852)",UNHBEH,BINGE,0107000,01073001100,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073001200,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.4,9.1,9.7,,,2876,"(33.5278767706, -86.8604161686)",UNHBEH,BINGE,0107000,01073001200,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073001400,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,8.9,8.5,9.3,,,2181,"(33.5261497258, -86.835146606)",UNHBEH,BINGE,0107000,01073001400,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073001500,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.4,9.1,9.8,,,3189,"(33.5298727342, -86.8197191685)",UNHBEH,BINGE,0107000,01073001500,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073001600,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,8.2,7.7,8.6,,,3390,"(33.5372993423, -86.8036590482)",UNHBEH,BINGE,0107000,01073001600,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073001902,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.3,8.9,9.8,,,1894,"(33.5532050997, -86.7429801603)",UNHBEH,BINGE,0107000,01073001902,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073002000,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.1,8.7,9.6,,,3885,"(33.5541574106, -86.7167229915)",UNHBEH,BINGE,0107000,01073002000,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073002100,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,10.0,9.6,10.6,,,3186,"(33.5650015942, -86.7101024766)",UNHBEH,BINGE,0107000,01073002100,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073002200,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,10.1,9.7,10.6,,,2630,"(33.5521301205, -86.7276759508)",UNHBEH,BINGE,0107000,01073002200,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073002303,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,8.9,8.5,9.4,,,2936,"(33.5383153207, -86.7270445428)",UNHBEH,BINGE,0107000,01073002303,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073002305,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,16.3,15.8,16.7,,,2952,"(33.5333415976, -86.7479566084)",UNHBEH,BINGE,0107000,01073002305,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073002306,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,15.6,15.2,16.1,,,3257,"(33.5213873564, -86.7490031289)",UNHBEH,BINGE,0107000,01073002306,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073002400,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,11.1,10.8,11.5,,,3629,"(33.5260748309, -86.7830315488)",UNHBEH,BINGE,0107000,01073002400,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073002700,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,13.6,13.0,14.2,,,3992,"(33.5176008419, -86.8106887452)",UNHBEH,BINGE,0107000,01073002700,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073002900,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,8.3,7.8,8.8,,,2064,"(33.5132498864, -86.83004749)",UNHBEH,BINGE,0107000,01073002900,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003001,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,14.3,13.3,15.3,,,3779,"(33.5125158094, -86.8577164946)",UNHBEH,BINGE,0107000,01073003001,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003002,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,7.4,7.0,7.8,,,2203,"(33.512258109, -86.8441439907)",UNHBEH,BINGE,0107000,01073003002,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003100,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.5,9.1,10.0,,,3637,"(33.5059655756, -86.8745506086)",UNHBEH,BINGE,0107000,01073003100,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003200,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,7.6,7.3,8.0,,,931,"(33.5094018502, -86.8859081961)",UNHBEH,BINGE,0107000,01073003200,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003300,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,8.4,8.0,8.9,,,947,"(33.5171261108, -86.8913819749)",UNHBEH,BINGE,0107000,01073003300,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073002305,Obesity among adults aged >=18 Years,%,CrdPrv,Crude prevalence,31.7,30.4,33.0,,,2952,"(33.5333415976, -86.7479566084)",UNHBEH,OBESITY,0107000,01073002305,Obesity
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003400,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,8.8,8.3,9.3,,,2477,"(33.5052229234, -86.9014844656)",UNHBEH,BINGE,0107000,01073003400,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003500,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,10.0,9.5,10.5,,,2780,"(33.5065714011, -86.9195910063)",UNHBEH,BINGE,0107000,01073003500,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003600,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.9,9.4,10.3,,,4683,"(33.48476397, -86.8981392947)",UNHBEH,BINGE,0107000,01073003600,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003700,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,10.3,9.9,10.7,,,5063,"(33.4969018589, -86.8907729426)",UNHBEH,BINGE,0107000,01073003700,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003802,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,10.3,9.9,10.7,,,5409,"(33.4785707794, -86.890000907)",UNHBEH,BINGE,0107000,01073003802,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003803,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.2,8.7,9.7,,,4199,"(33.485945214, -86.869692186)",UNHBEH,BINGE,0107000,01073003803,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073003900,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.5,9.1,9.8,,,1783,"(33.4989959327, -86.8647600038)",UNHBEH,BINGE,0107000,01073003900,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073004000,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,8.5,8.0,9.0,,,3772,"(33.4953246015, -86.8516232073)",UNHBEH,BINGE,0107000,01073004000,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073004200,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,9.1,8.8,9.4,,,2341,"(33.5007439361, -86.8270720379)",UNHBEH,BINGE,0107000,01073004200,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073004500,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,13.8,12.9,14.8,,,5003,"(33.5041857556, -86.8033798346)",UNHBEH,BINGE,0107000,01073004500,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073004701,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,16.4,15.7,17.2,,,3480,"(33.5075242148, -86.7836675838)",UNHBEH,BINGE,0107000,01073004701,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073004702,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,15.3,14.9,15.7,,,2944,"(33.5119902661, -86.7694550989)",UNHBEH,BINGE,0107000,01073004702,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073004800,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,14.1,13.7,14.5,,,1861,"(33.4989064008, -86.78269914)",UNHBEH,BINGE,0107000,01073004800,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073004901,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,17.0,16.4,17.7,,,1167,"(33.4971595645, -86.7917440668)",UNHBEH,BINGE,0107000,01073004901,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073004902,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,16.3,15.6,17.1,,,3146,"(33.4935824043, -86.8009294603)",UNHBEH,BINGE,0107000,01073004902,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073005000,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,16.1,15.6,16.7,,,3482,"(33.4866689795, -86.8173262831)",UNHBEH,BINGE,0107000,01073005000,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073005101,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,7.9,7.5,8.3,,,1507,"(33.4945909008, -86.834763936)",UNHBEH,BINGE,0107000,01073005101,Binge Drinking
+2015,AL,Alabama,Birmingham,Census Tract,BRFSS,Unhealthy Behaviors,0107000-01073005103,Binge drinking among adults aged >=18 Years,%,CrdPrv,Crude prevalence,7.7,7.4,8.0,,,2587,"(33.485714885, -86.8327817467)",UNHBEH,BINGE,0107000,01073005103,Binge Drinking
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sample_json.json b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sample_json.json
new file mode 100644 (file)
index 0000000..0a1eab9
--- /dev/null
@@ -0,0 +1,5154 @@
+{
+    "meta": {
+        "limit": 100,
+        "offset": 0,
+        "total_count": 100
+    },
+    "objects": [
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Wisconsin",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "709 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.baldwin.senate.gov/feedback",
+                "fax": "202-225-6942",
+                "office": "709 Hart Senate Office Building",
+                "rss_url": "http://www.baldwin.senate.gov/rss/feeds/?type=all"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "B001230",
+                "birthday": "1962-02-11",
+                "cspanid": 57884,
+                "firstname": "Tammy",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Baldwin",
+                "link": "https://www.govtrack.us/congress/members/tammy_baldwin/400013",
+                "middlename": "",
+                "name": "Sen. Tammy Baldwin [D-WI]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00004367",
+                "pvsid": "3470",
+                "sortname": "Baldwin, Tammy (Sen.) [D-WI]",
+                "twitterid": "SenatorBaldwin",
+                "youtubeid": "witammybaldwin"
+            },
+            "phone": "202-224-5653",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "WI",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.baldwin.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Ohio",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "713 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.brown.senate.gov/contact/",
+                "fax": "202-228-6321",
+                "office": "713 Hart Senate Office Building",
+                "rss_url": "http://www.brown.senate.gov/rss/feeds/?type=all&amp;"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "B000944",
+                "birthday": "1952-11-09",
+                "cspanid": 5051,
+                "firstname": "Sherrod",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Brown",
+                "link": "https://www.govtrack.us/congress/members/sherrod_brown/400050",
+                "middlename": "",
+                "name": "Sen. Sherrod Brown [D-OH]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00003535",
+                "pvsid": "27018",
+                "sortname": "Brown, Sherrod (Sen.) [D-OH]",
+                "twitterid": "SenSherrodBrown",
+                "youtubeid": "SherrodBrownOhio"
+            },
+            "phone": "202-224-2315",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "OH",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.brown.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Maryland",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "509 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.cardin.senate.gov/contact/",
+                "fax": "202-224-1651",
+                "office": "509 Hart Senate Office Building",
+                "rss_url": "http://www.cardin.senate.gov/rss/feeds/?type=all"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "C000141",
+                "birthday": "1943-10-05",
+                "cspanid": 4004,
+                "firstname": "Benjamin",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Cardin",
+                "link": "https://www.govtrack.us/congress/members/benjamin_cardin/400064",
+                "middlename": "L.",
+                "name": "Sen. Benjamin Cardin [D-MD]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00001955",
+                "pvsid": "26888",
+                "sortname": "Cardin, Benjamin (Sen.) [D-MD]",
+                "twitterid": "SenatorCardin",
+                "youtubeid": "senatorcardin"
+            },
+            "phone": "202-224-4524",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "MD",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.cardin.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Arizona",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "413 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.flake.senate.gov/public/index.cfm/contact-jeff",
+                "fax": "202-226-4386",
+                "office": "413 Russell Senate Office Building",
+                "rss_url": "http://www.flake.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "F000444",
+                "birthday": "1962-12-31",
+                "cspanid": 87582,
+                "firstname": "Jeff",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Flake",
+                "link": "https://www.govtrack.us/congress/members/jeff_flake/400134",
+                "middlename": "",
+                "name": "Sen. Jeff Flake [R-AZ]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00009573",
+                "pvsid": "28128",
+                "sortname": "Flake, Jeff (Sen.) [R-AZ]",
+                "twitterid": "JeffFlake",
+                "youtubeid": "flakeoffice"
+            },
+            "phone": "202-224-4521",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "AZ",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.flake.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for New Jersey",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "528 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.menendez.senate.gov/contact",
+                "fax": "202-228-2197",
+                "office": "528 Hart Senate Office Building",
+                "rss_url": "http://www.menendez.senate.gov/rss/feeds/index.cfm?type=news"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "M000639",
+                "birthday": "1954-01-01",
+                "cspanid": 29608,
+                "firstname": "Robert",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Menéndez",
+                "link": "https://www.govtrack.us/congress/members/robert_menendez/400272",
+                "middlename": "",
+                "name": "Sen. Robert “Bob” Menéndez [D-NJ]",
+                "namemod": "",
+                "nickname": "Bob",
+                "osid": "N00000699",
+                "pvsid": "26961",
+                "sortname": "Menéndez, Robert “Bob” (Sen.) [D-NJ]",
+                "twitterid": "SenatorMenendez",
+                "youtubeid": "SenatorMenendezNJ"
+            },
+            "phone": "202-224-4744",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "NJ",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.menendez.senate.gov"
+        },
+        {
+            "caucus": "Democrat",
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Vermont",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "332 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.sanders.senate.gov/contact/",
+                "fax": "202-228-0776",
+                "office": "332 Dirksen Senate Office Building",
+                "rss_url": "http://www.sanders.senate.gov/rss/"
+            },
+            "leadership_title": null,
+            "party": "Independent",
+            "person": {
+                "bioguideid": "S000033",
+                "birthday": "1941-09-08",
+                "cspanid": 994,
+                "firstname": "Bernard",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Sanders",
+                "link": "https://www.govtrack.us/congress/members/bernard_sanders/400357",
+                "middlename": "",
+                "name": "Sen. Bernard “Bernie” Sanders [I-VT]",
+                "namemod": "",
+                "nickname": "Bernie",
+                "osid": "N00000528",
+                "pvsid": "27110",
+                "sortname": "Sanders, Bernard “Bernie” (Sen.) [I-VT]",
+                "twitterid": "SenSanders",
+                "youtubeid": "senatorsanders"
+            },
+            "phone": "202-224-5141",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "VT",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.sanders.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Washington",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "511 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.cantwell.senate.gov/public/index.cfm/email-maria",
+                "fax": "202-228-0514",
+                "office": "511 Hart Senate Office Building",
+                "rss_url": "http://www.cantwell.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "C000127",
+                "birthday": "1958-10-13",
+                "cspanid": 26137,
+                "firstname": "Maria",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Cantwell",
+                "link": "https://www.govtrack.us/congress/members/maria_cantwell/300018",
+                "middlename": "",
+                "name": "Sen. Maria Cantwell [D-WA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00007836",
+                "pvsid": "27122",
+                "sortname": "Cantwell, Maria (Sen.) [D-WA]",
+                "twitterid": "SenatorCantwell",
+                "youtubeid": "SenatorCantwell"
+            },
+            "phone": "202-224-3441",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "WA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.cantwell.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Delaware",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "513 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.carper.senate.gov/public/index.cfm/email-senator-carper",
+                "fax": "202-228-2190",
+                "office": "513 Hart Senate Office Building",
+                "rss_url": "http://www.carper.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "C000174",
+                "birthday": "1947-01-23",
+                "cspanid": 663,
+                "firstname": "Thomas",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Carper",
+                "link": "https://www.govtrack.us/congress/members/thomas_carper/300019",
+                "middlename": "Richard",
+                "name": "Sen. Thomas Carper [D-DE]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00012508",
+                "pvsid": "22421",
+                "sortname": "Carper, Thomas (Sen.) [D-DE]",
+                "twitterid": "SenatorCarper",
+                "youtubeid": "senatorcarper"
+            },
+            "phone": "202-224-2441",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "DE",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.carper.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for California",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "331 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.feinstein.senate.gov/public/index.cfm/e-mail-me",
+                "fax": "202-228-3954",
+                "office": "331 Hart Senate Office Building",
+                "rss_url": "http://www.feinstein.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "F000062",
+                "birthday": "1933-06-22",
+                "cspanid": 13061,
+                "firstname": "Dianne",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Feinstein",
+                "link": "https://www.govtrack.us/congress/members/dianne_feinstein/300043",
+                "middlename": "",
+                "name": "Sen. Dianne Feinstein [D-CA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00007364",
+                "pvsid": "53273",
+                "sortname": "Feinstein, Dianne (Sen.) [D-CA]",
+                "twitterid": "SenFeinstein",
+                "youtubeid": "SenatorFeinstein"
+            },
+            "phone": "202-224-3841",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "CA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.feinstein.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Utah",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "104 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.hatch.senate.gov/public/index.cfm/contact?p=Email-Orrin",
+                "fax": "202-224-6331",
+                "office": "104 Hart Senate Office Building",
+                "rss_url": "http://www.hatch.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "H000338",
+                "birthday": "1934-03-22",
+                "cspanid": 189,
+                "firstname": "Orrin",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Hatch",
+                "link": "https://www.govtrack.us/congress/members/orrin_hatch/300052",
+                "middlename": "G.",
+                "name": "Sen. Orrin Hatch [R-UT]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00009869",
+                "pvsid": "53352",
+                "sortname": "Hatch, Orrin (Sen.) [R-UT]",
+                "twitterid": "SenOrrinHatch",
+                "youtubeid": "SenatorOrrinHatch"
+            },
+            "phone": "202-224-5251",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "UT",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.hatch.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Florida",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "716 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.billnelson.senate.gov/contact-bill",
+                "fax": "202-228-2183",
+                "office": "716 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "N000032",
+                "birthday": "1942-09-29",
+                "cspanid": 1931,
+                "firstname": "Bill",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Nelson",
+                "link": "https://www.govtrack.us/congress/members/bill_nelson/300078",
+                "middlename": "",
+                "name": "Sen. Bill Nelson [D-FL]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00009926",
+                "pvsid": "1606",
+                "sortname": "Nelson, Bill (Sen.) [D-FL]",
+                "twitterid": "SenBillNelson",
+                "youtubeid": "senbillnelson"
+            },
+            "phone": "202-224-5274",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "FL",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.billnelson.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Michigan",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "731 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.stabenow.senate.gov/contact",
+                "fax": "202-228-0325",
+                "office": "731 Hart Senate Office Building",
+                "rss_url": "http://stabenow.senate.gov/rss/?p=news"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "S000770",
+                "birthday": "1950-04-29",
+                "cspanid": 45451,
+                "firstname": "Debbie",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Stabenow",
+                "link": "https://www.govtrack.us/congress/members/debbie_stabenow/300093",
+                "middlename": "Ann",
+                "name": "Sen. Debbie Stabenow [D-MI]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00004118",
+                "pvsid": "515",
+                "sortname": "Stabenow, Debbie (Sen.) [D-MI]",
+                "twitterid": "SenStabenow",
+                "youtubeid": "senatorstabenow"
+            },
+            "phone": "202-224-4822",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "MI",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.stabenow.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Connecticut",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "136 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.murphy.senate.gov/contact",
+                "fax": "202-225-5933",
+                "office": "136 Hart Senate Office Building",
+                "rss_url": "http://www.theday.com/article/20121216/nws12/312169935/1069/rss"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "M001169",
+                "birthday": "1973-08-03",
+                "cspanid": 1021270,
+                "firstname": "Christopher",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Murphy",
+                "link": "https://www.govtrack.us/congress/members/christopher_murphy/412194",
+                "middlename": "S.",
+                "name": "Sen. Christopher Murphy [D-CT]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00027566",
+                "pvsid": "17189",
+                "sortname": "Murphy, Christopher (Sen.) [D-CT]",
+                "twitterid": "senmurphyoffice",
+                "youtubeid": "senchrismurphy"
+            },
+            "phone": "202-224-4041",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "CT",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.murphy.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Hawaii",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "730 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.hirono.senate.gov/contact",
+                "fax": "202-225-4987",
+                "office": "730 Hart Senate Office Building",
+                "rss_url": "http://www.hirono.senate.gov/rss/feeds/?type=all"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "H001042",
+                "birthday": "1947-11-03",
+                "cspanid": 91216,
+                "firstname": "Mazie",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Hirono",
+                "link": "https://www.govtrack.us/congress/members/mazie_hirono/412200",
+                "middlename": "K.",
+                "name": "Sen. Mazie Hirono [D-HI]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00028139",
+                "pvsid": "1677",
+                "sortname": "Hirono, Mazie (Sen.) [D-HI]",
+                "twitterid": "MazieHirono",
+                "youtubeid": "CongresswomanHirono"
+            },
+            "phone": "202-224-6361",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "HI",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.hirono.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Indiana",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "720 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.donnelly.senate.gov/contact/email-joe",
+                "fax": "202-225-6798",
+                "office": "720 Hart Senate Office Building",
+                "rss_url": "http://www.donnelly.senate.gov/rss/feeds/?type=all"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "D000607",
+                "birthday": "1955-09-28",
+                "cspanid": 1012000,
+                "firstname": "Joe",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Donnelly",
+                "link": "https://www.govtrack.us/congress/members/joe_donnelly/412205",
+                "middlename": "",
+                "name": "Sen. Joe Donnelly [D-IN]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00026586",
+                "pvsid": "34212",
+                "sortname": "Donnelly, Joe (Sen.) [D-IN]",
+                "twitterid": "SenDonnelly",
+                "youtubeid": "sendonnelly"
+            },
+            "phone": "202-224-4814",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "IN",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.donnelly.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Nevada",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "324 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.heller.senate.gov/public/index.cfm/contact-form",
+                "fax": "202-228-6753",
+                "office": "324 Hart Senate Office Building",
+                "rss_url": "http://www.heller.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "H001041",
+                "birthday": "1960-05-10",
+                "cspanid": 1012368,
+                "firstname": "Dean",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Heller",
+                "link": "https://www.govtrack.us/congress/members/dean_heller/412218",
+                "middlename": "",
+                "name": "Sen. Dean Heller [R-NV]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00027522",
+                "pvsid": "2291",
+                "sortname": "Heller, Dean (Sen.) [R-NV]",
+                "twitterid": "SenDeanHeller",
+                "youtubeid": "SenDeanHeller"
+            },
+            "phone": "202-224-6244",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "NV",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.heller.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for New York",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "478 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.gillibrand.senate.gov/contact/email-me",
+                "fax": "202-228-0282",
+                "office": "478 Russell Senate Office Building",
+                "rss_url": "http://www.gillibrand.senate.gov/rss/"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "G000555",
+                "birthday": "1966-12-09",
+                "cspanid": 1022862,
+                "firstname": "Kirsten",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Gillibrand",
+                "link": "https://www.govtrack.us/congress/members/kirsten_gillibrand/412223",
+                "middlename": "E.",
+                "name": "Sen. Kirsten Gillibrand [D-NY]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00027658",
+                "pvsid": "65147",
+                "sortname": "Gillibrand, Kirsten (Sen.) [D-NY]",
+                "twitterid": "SenGillibrand",
+                "youtubeid": "KirstenEGillibrand"
+            },
+            "phone": "202-224-4451",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "NY",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.gillibrand.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Minnesota",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "302 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.klobuchar.senate.gov/public/index.cfm/contact",
+                "fax": "202-228-2186",
+                "office": "302 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "K000367",
+                "birthday": "1960-05-25",
+                "cspanid": 83701,
+                "firstname": "Amy",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Klobuchar",
+                "link": "https://www.govtrack.us/congress/members/amy_klobuchar/412242",
+                "middlename": "Jean",
+                "name": "Sen. Amy Klobuchar [D-MN]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00027500",
+                "pvsid": "65092",
+                "sortname": "Klobuchar, Amy (Sen.) [D-MN]",
+                "twitterid": "SenAmyKlobuchar",
+                "youtubeid": "senatorklobuchar"
+            },
+            "phone": "202-224-3244",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "MN",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.klobuchar.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Missouri",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "503 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.mccaskill.senate.gov/contact",
+                "fax": "202-228-6326",
+                "office": "503 Hart Senate Office Building",
+                "rss_url": "http://mccaskill.senate.gov/rss/?p=news"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "M001170",
+                "birthday": "1953-07-24",
+                "cspanid": 44501,
+                "firstname": "Claire",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "McCaskill",
+                "link": "https://www.govtrack.us/congress/members/claire_mccaskill/412243",
+                "middlename": "",
+                "name": "Sen. Claire McCaskill [D-MO]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00027694",
+                "pvsid": "2109",
+                "sortname": "McCaskill, Claire (Sen.) [D-MO]",
+                "twitterid": "McCaskillOffice",
+                "youtubeid": "SenatorMcCaskill"
+            },
+            "phone": "202-224-6154",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "MO",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.mccaskill.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Montana",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "311 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.tester.senate.gov/?p=email_senator",
+                "fax": "202-224-8594",
+                "office": "311 Hart Senate Office Building",
+                "rss_url": "http://www.tester.senate.gov/rss/?p=hot_topic"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "T000464",
+                "birthday": "1956-08-21",
+                "cspanid": 1020176,
+                "firstname": "Jon",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Tester",
+                "link": "https://www.govtrack.us/congress/members/jon_tester/412244",
+                "middlename": "",
+                "name": "Sen. Jon Tester [D-MT]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00027605",
+                "pvsid": "20928",
+                "sortname": "Tester, Jon (Sen.) [D-MT]",
+                "twitterid": "SenatorTester",
+                "youtubeid": "senatorjontester"
+            },
+            "phone": "202-224-2644",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "MT",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.tester.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Pennsylvania",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "393 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.casey.senate.gov/contact/",
+                "fax": "202-228-0604",
+                "office": "393 Russell Senate Office Building",
+                "rss_url": "http://www.casey.senate.gov/rss/feeds/?all"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "C001070",
+                "birthday": "1960-04-13",
+                "cspanid": 47036,
+                "firstname": "Robert",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Casey",
+                "link": "https://www.govtrack.us/congress/members/robert_casey/412246",
+                "middlename": "P.",
+                "name": "Sen. Robert “Bob” Casey [D-PA]",
+                "namemod": "Jr.",
+                "nickname": "Bob",
+                "osid": "N00027503",
+                "pvsid": "2541",
+                "sortname": "Casey, Robert “Bob” (Sen.) [D-PA]",
+                "twitterid": "SenBobCasey",
+                "youtubeid": "SenatorBobCasey"
+            },
+            "phone": "202-224-6324",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "PA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.casey.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Rhode Island",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "530 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.whitehouse.senate.gov/contact/email-sheldon",
+                "fax": "202-228-6362",
+                "office": "530 Hart Senate Office Building",
+                "rss_url": "http://www.whitehouse.senate.gov/rss/feeds/?type=all&amp;cachebuster=1"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "W000802",
+                "birthday": "1955-10-20",
+                "cspanid": 92235,
+                "firstname": "Sheldon",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Whitehouse",
+                "link": "https://www.govtrack.us/congress/members/sheldon_whitehouse/412247",
+                "middlename": "",
+                "name": "Sen. Sheldon Whitehouse [D-RI]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00027533",
+                "pvsid": "2572",
+                "sortname": "Whitehouse, Sheldon (Sen.) [D-RI]",
+                "twitterid": "SenWhitehouse",
+                "youtubeid": "SenatorWhitehouse"
+            },
+            "phone": "202-224-2921",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "RI",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.whitehouse.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Tennessee",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "425 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.corker.senate.gov/public/index.cfm/emailme",
+                "fax": "202-228-0566",
+                "office": "425 Dirksen Senate Office Building",
+                "rss_url": "http://www.corker.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "C001071",
+                "birthday": "1952-08-24",
+                "cspanid": 1021114,
+                "firstname": "Bob",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Corker",
+                "link": "https://www.govtrack.us/congress/members/bob_corker/412248",
+                "middlename": "",
+                "name": "Sen. Bob Corker [R-TN]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00027441",
+                "pvsid": "65905",
+                "sortname": "Corker, Bob (Sen.) [R-TN]",
+                "twitterid": "SenBobCorker",
+                "youtubeid": "senatorcorker"
+            },
+            "phone": "202-224-3344",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "TN",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.corker.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Wyoming",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "307 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.barrasso.senate.gov/public/index.cfm/contact-form",
+                "fax": "202-224-1724",
+                "office": "307 Dirksen Senate Office Building",
+                "rss_url": "http://www.barrasso.senate.gov/public/index.cfm?FuseAction=Rss.Feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "B001261",
+                "birthday": "1952-07-21",
+                "cspanid": 1024777,
+                "firstname": "John",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Barrasso",
+                "link": "https://www.govtrack.us/congress/members/john_barrasso/412251",
+                "middlename": "A.",
+                "name": "Sen. John Barrasso [R-WY]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00006236",
+                "pvsid": "52662",
+                "sortname": "Barrasso, John (Sen.) [R-WY]",
+                "twitterid": "SenJohnBarrasso",
+                "youtubeid": "barrassowyo"
+            },
+            "phone": "202-224-6441",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "WY",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.barrasso.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for New Mexico",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "303 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.heinrich.senate.gov/contact",
+                "fax": "202-225-4975",
+                "office": "303 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "H001046",
+                "birthday": "1971-10-17",
+                "cspanid": 1030686,
+                "firstname": "Martin",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Heinrich",
+                "link": "https://www.govtrack.us/congress/members/martin_heinrich/412281",
+                "middlename": "",
+                "name": "Sen. Martin Heinrich [D-NM]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00029835",
+                "pvsid": "74517",
+                "sortname": "Heinrich, Martin (Sen.) [D-NM]",
+                "twitterid": "MartinHeinrich",
+                "youtubeid": "SenMartinHeinrich"
+            },
+            "phone": "202-224-5521",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "NM",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.heinrich.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for West Virginia",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "306 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.manchin.senate.gov/public/index.cfm/contact-form",
+                "fax": "202-228-0002",
+                "office": "306 Hart Senate Office Building",
+                "rss_url": "http://www.manchin.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "M001183",
+                "birthday": "1947-08-24",
+                "cspanid": 62864,
+                "firstname": "Joe",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Manchin",
+                "link": "https://www.govtrack.us/congress/members/joe_manchin/412391",
+                "middlename": "",
+                "name": "Sen. Joe Manchin [D-WV]",
+                "namemod": "III",
+                "nickname": "",
+                "osid": "N00032838",
+                "pvsid": "7547",
+                "sortname": "Manchin, Joe (Sen.) [D-WV]",
+                "twitterid": "Sen_JoeManchin",
+                "youtubeid": "SenatorJoeManchin"
+            },
+            "phone": "202-224-3954",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "WV",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.manchin.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Massachusetts",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "317 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.warren.senate.gov/?p=email_senator",
+                "fax": "202-228-2072",
+                "office": "317 Hart Senate Office Building",
+                "rss_url": "http://www.warren.senate.gov/rss/?p=hot_topic"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "W000817",
+                "birthday": "1949-06-22",
+                "cspanid": 1023023,
+                "firstname": "Elizabeth",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Warren",
+                "link": "https://www.govtrack.us/congress/members/elizabeth_warren/412542",
+                "middlename": "",
+                "name": "Sen. Elizabeth Warren [D-MA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00033492",
+                "pvsid": "141272",
+                "sortname": "Warren, Elizabeth (Sen.) [D-MA]",
+                "twitterid": "SenWarren",
+                "youtubeid": "senelizabethwarren"
+            },
+            "phone": "202-224-4543",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "MA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.warren.senate.gov"
+        },
+        {
+            "caucus": "Democrat",
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Maine",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "133 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.king.senate.gov/contact",
+                "fax": "202-224-1946",
+                "office": "133 Hart Senate Office Building",
+                "rss_url": "http://www.king.senate.gov/rss/feeds/?type=all"
+            },
+            "leadership_title": null,
+            "party": "Independent",
+            "person": {
+                "bioguideid": "K000383",
+                "birthday": "1944-03-31",
+                "cspanid": 37413,
+                "firstname": "Angus",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "King",
+                "link": "https://www.govtrack.us/congress/members/angus_king/412545",
+                "middlename": "",
+                "name": "Sen. Angus King [I-ME]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00034580",
+                "pvsid": "22381",
+                "sortname": "King, Angus (Sen.) [I-ME]",
+                "twitterid": "SenAngusKing",
+                "youtubeid": "SenatorAngusKing"
+            },
+            "phone": "202-224-5344",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "ME",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.king.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for North Dakota",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "516 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.heitkamp.senate.gov/public/index.cfm/contact",
+                "fax": "202-224-7776",
+                "office": "516 Hart Senate Office Building",
+                "rss_url": "http://www.heitkamp.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "H001069",
+                "birthday": "1955-10-30",
+                "cspanid": 95414,
+                "firstname": "Heidi",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Heitkamp",
+                "link": "https://www.govtrack.us/congress/members/heidi_heitkamp/412554",
+                "middlename": "",
+                "name": "Sen. Heidi Heitkamp [D-ND]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00033782",
+                "pvsid": "41716",
+                "sortname": "Heitkamp, Heidi (Sen.) [D-ND]",
+                "twitterid": "SenatorHeitkamp",
+                "youtubeid": "senatorheidiheitkamp"
+            },
+            "phone": "202-224-2043",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "ND",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.heitkamp.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Nebraska",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "454 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.fischer.senate.gov/public/index.cfm/contact",
+                "fax": "202-228-1325",
+                "office": "454 Russell Senate Office Building",
+                "rss_url": "http://www.fischer.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "F000463",
+                "birthday": "1951-03-01",
+                "cspanid": 1034067,
+                "firstname": "Deb",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Fischer",
+                "link": "https://www.govtrack.us/congress/members/deb_fischer/412556",
+                "middlename": "",
+                "name": "Sen. Deb Fischer [R-NE]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00033443",
+                "pvsid": "41963",
+                "sortname": "Fischer, Deb (Sen.) [R-NE]",
+                "twitterid": "SenatorFischer",
+                "youtubeid": "senatordebfischer"
+            },
+            "phone": "202-224-6551",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "NE",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.fischer.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Texas",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "404 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.cruz.senate.gov/?p=form&id=16",
+                "fax": "202-228-3398",
+                "office": "404 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "C001098",
+                "birthday": "1970-12-22",
+                "cspanid": 1019953,
+                "firstname": "Ted",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Cruz",
+                "link": "https://www.govtrack.us/congress/members/ted_cruz/412573",
+                "middlename": "",
+                "name": "Sen. Ted Cruz [R-TX]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00033085",
+                "pvsid": "135705",
+                "sortname": "Cruz, Ted (Sen.) [R-TX]",
+                "twitterid": "SenTedCruz",
+                "youtubeid": "sentedcruz"
+            },
+            "phone": "202-224-5922",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "TX",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.cruz.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Virginia",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "231 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.kaine.senate.gov/contact",
+                "fax": "202-228-6363",
+                "office": "231 Russell Senate Office Building",
+                "rss_url": "http://www.kaine.senate.gov/rss/feeds/?type=all"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "K000384",
+                "birthday": "1958-02-26",
+                "cspanid": 49219,
+                "firstname": "Timothy",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Kaine",
+                "link": "https://www.govtrack.us/congress/members/timothy_kaine/412582",
+                "middlename": "",
+                "name": "Sen. Timothy Kaine [D-VA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00033177",
+                "pvsid": "50772",
+                "sortname": "Kaine, Timothy (Sen.) [D-VA]",
+                "twitterid": null,
+                "youtubeid": "SenatorTimKaine"
+            },
+            "phone": "202-224-4024",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2013-01-03",
+            "state": "VA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.kaine.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                113,
+                114,
+                115
+            ],
+            "current": true,
+            "description": "Senior Senator for Mississippi",
+            "district": null,
+            "enddate": "2019-01-03",
+            "extra": {
+                "address": "555 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.wicker.senate.gov/public/index.cfm/contact",
+                "fax": "202-228-0378",
+                "office": "555 Dirksen Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "W000437",
+                "birthday": "1951-07-05",
+                "cspanid": 18203,
+                "firstname": "Roger",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Wicker",
+                "link": "https://www.govtrack.us/congress/members/roger_wicker/400432",
+                "middlename": "F.",
+                "name": "Sen. Roger Wicker [R-MS]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00003280",
+                "pvsid": "21926",
+                "sortname": "Wicker, Roger (Sen.) [R-MS]",
+                "twitterid": "SenatorWicker",
+                "youtubeid": "SenatorWicker"
+            },
+            "phone": "202-224-6253",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class1",
+            "senator_class_label": "Class 1",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2013-01-03",
+            "state": "MS",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.wicker.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Tennessee",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "455 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.alexander.senate.gov/public/index.cfm?p=Email",
+                "fax": "202-228-3398",
+                "office": "455 Dirksen Senate Office Building",
+                "rss_url": "http://www.alexander.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "A000360",
+                "birthday": "1940-07-03",
+                "cspanid": 5,
+                "firstname": "Lamar",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Alexander",
+                "link": "https://www.govtrack.us/congress/members/lamar_alexander/300002",
+                "middlename": "",
+                "name": "Sen. Lamar Alexander [R-TN]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00009888",
+                "pvsid": "15691",
+                "sortname": "Alexander, Lamar (Sen.) [R-TN]",
+                "twitterid": "SenAlexander",
+                "youtubeid": "lamaralexander"
+            },
+            "phone": "202-224-4944",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "TN",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.alexander.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Maine",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "413 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.collins.senate.gov/contact",
+                "fax": "202-224-2693",
+                "office": "413 Dirksen Senate Office Building",
+                "rss_url": "http://www.collins.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "C001035",
+                "birthday": "1952-12-07",
+                "cspanid": 45738,
+                "firstname": "Susan",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Collins",
+                "link": "https://www.govtrack.us/congress/members/susan_collins/300025",
+                "middlename": "M.",
+                "name": "Sen. Susan Collins [R-ME]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00000491",
+                "pvsid": "379",
+                "sortname": "Collins, Susan (Sen.) [R-ME]",
+                "twitterid": "SenatorCollins",
+                "youtubeid": "SenatorSusanCollins"
+            },
+            "phone": "202-224-2523",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "ME",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.collins.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Texas",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "517 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.cornyn.senate.gov/contact",
+                "fax": "202-228-2856",
+                "office": "517 Hart Senate Office Building",
+                "rss_url": "http://www.cornyn.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": "Majority Whip",
+            "party": "Republican",
+            "person": {
+                "bioguideid": "C001056",
+                "birthday": "1952-02-02",
+                "cspanid": 93131,
+                "firstname": "John",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Cornyn",
+                "link": "https://www.govtrack.us/congress/members/john_cornyn/300027",
+                "middlename": "",
+                "name": "Sen. John Cornyn [R-TX]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00024852",
+                "pvsid": "15375",
+                "sortname": "Cornyn, John (Sen.) [R-TX]",
+                "twitterid": "JohnCornyn",
+                "youtubeid": "senjohncornyn"
+            },
+            "phone": "202-224-2934",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "TX",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.cornyn.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Illinois",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "711 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.durbin.senate.gov/contact/",
+                "fax": "202-228-0400",
+                "office": "711 Hart Senate Office Building",
+                "rss_url": "http://durbin.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": "Minority Whip",
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "D000563",
+                "birthday": "1944-11-21",
+                "cspanid": 6741,
+                "firstname": "Richard",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Durbin",
+                "link": "https://www.govtrack.us/congress/members/richard_durbin/300038",
+                "middlename": "J.",
+                "name": "Sen. Richard Durbin [D-IL]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00004981",
+                "pvsid": "26847",
+                "sortname": "Durbin, Richard (Sen.) [D-IL]",
+                "twitterid": "SenatorDurbin",
+                "youtubeid": "SenatorDurbin"
+            },
+            "phone": "202-224-2152",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "IL",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.durbin.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Wyoming",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "379A Russell Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.enzi.senate.gov/public/index.cfm/contact?p=e-mail-senator-enzi",
+                "fax": "202-228-0359",
+                "office": "379a Russell Senate Office Building",
+                "rss_url": "http://www.enzi.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "E000285",
+                "birthday": "1944-02-01",
+                "cspanid": 45824,
+                "firstname": "Michael",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Enzi",
+                "link": "https://www.govtrack.us/congress/members/michael_enzi/300041",
+                "middlename": "B.",
+                "name": "Sen. Michael Enzi [R-WY]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00006249",
+                "pvsid": "558",
+                "sortname": "Enzi, Michael (Sen.) [R-WY]",
+                "twitterid": "SenatorEnzi",
+                "youtubeid": "senatorenzi"
+            },
+            "phone": "202-224-3424",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "WY",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.enzi.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for South Carolina",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "290 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.lgraham.senate.gov/public/index.cfm/e-mail-senator-graham",
+                "fax": "202-224-3808",
+                "office": "290 Russell Senate Office Building",
+                "rss_url": "http://www.lgraham.senate.gov/public/index.cfm?FuseAction=Rss.Feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "G000359",
+                "birthday": "1955-07-09",
+                "cspanid": 36782,
+                "firstname": "Lindsey",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Graham",
+                "link": "https://www.govtrack.us/congress/members/lindsey_graham/300047",
+                "middlename": "O.",
+                "name": "Sen. Lindsey Graham [R-SC]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00009975",
+                "pvsid": "21992",
+                "sortname": "Graham, Lindsey (Sen.) [R-SC]",
+                "twitterid": "GrahamBlog",
+                "youtubeid": "USSenLindseyGraham"
+            },
+            "phone": "202-224-5972",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "SC",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.lgraham.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Oklahoma",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "205 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.inhofe.senate.gov/contact",
+                "fax": "202-228-0380",
+                "office": "205 Russell Senate Office Building",
+                "rss_url": "http://www.inhofe.senate.gov/rss/feeds/?type=all&amp;cachebuster=eea6c4d7%2d939c%2d5c1e%2db6c7aa3b8b291208"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "I000024",
+                "birthday": "1934-11-17",
+                "cspanid": 5619,
+                "firstname": "James",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Inhofe",
+                "link": "https://www.govtrack.us/congress/members/james_inhofe/300055",
+                "middlename": "M.",
+                "name": "Sen. James “Jim” Inhofe [R-OK]",
+                "namemod": "",
+                "nickname": "Jim",
+                "osid": "N00005582",
+                "pvsid": "27027",
+                "sortname": "Inhofe, James “Jim” (Sen.) [R-OK]",
+                "twitterid": "InhofePress",
+                "youtubeid": "jiminhofepressoffice"
+            },
+            "phone": "202-224-4721",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "OK",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.inhofe.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Kentucky",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "317 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.mcconnell.senate.gov/public/index.cfm?p=contact",
+                "fax": "202-224-2499",
+                "office": "317 Russell Senate Office Building",
+                "rss_url": "http://www.mcconnell.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": "Majority Leader",
+            "party": "Republican",
+            "person": {
+                "bioguideid": "M000355",
+                "birthday": "1942-02-20",
+                "cspanid": 2351,
+                "firstname": "Mitch",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "McConnell",
+                "link": "https://www.govtrack.us/congress/members/mitch_mcconnell/300072",
+                "middlename": "",
+                "name": "Sen. Mitch McConnell [R-KY]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00003389",
+                "pvsid": "53298",
+                "sortname": "McConnell, Mitch (Sen.) [R-KY]",
+                "twitterid": "McConnellPress",
+                "youtubeid": null
+            },
+            "phone": "202-224-2541",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "KY",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.mcconnell.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Rhode Island",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "728 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.reed.senate.gov/contact/",
+                "fax": "202-224-4680",
+                "office": "728 Hart Senate Office Building",
+                "rss_url": "https://www.reed.senate.gov//rss/feeds/?type=all"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "R000122",
+                "birthday": "1949-11-12",
+                "cspanid": 24239,
+                "firstname": "John",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Reed",
+                "link": "https://www.govtrack.us/congress/members/john_reed/300081",
+                "middlename": "F.",
+                "name": "Sen. John “Jack” Reed [D-RI]",
+                "namemod": "",
+                "nickname": "Jack",
+                "osid": "N00000362",
+                "pvsid": "27060",
+                "sortname": "Reed, John “Jack” (Sen.) [D-RI]",
+                "twitterid": "SenJackReed",
+                "youtubeid": "SenatorReed"
+            },
+            "phone": "202-224-4642",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "RI",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.reed.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Kansas",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "109 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.roberts.senate.gov/public/?p=EmailPat",
+                "fax": "202-224-3514",
+                "office": "109 Hart Senate Office Building",
+                "rss_url": "http://www.roberts.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "R000307",
+                "birthday": "1936-04-20",
+                "cspanid": 16354,
+                "firstname": "Pat",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Roberts",
+                "link": "https://www.govtrack.us/congress/members/pat_roberts/300083",
+                "middlename": "",
+                "name": "Sen. Pat Roberts [R-KS]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00005285",
+                "pvsid": "26866",
+                "sortname": "Roberts, Pat (Sen.) [R-KS]",
+                "twitterid": "SenPatRoberts",
+                "youtubeid": "SenPatRoberts"
+            },
+            "phone": "202-224-4774",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "KS",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.roberts.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for West Virginia",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "172 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.capito.senate.gov/contact/contact-shelley",
+                "fax": "202-225-7856",
+                "office": "172 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "C001047",
+                "birthday": "1953-11-26",
+                "cspanid": 83737,
+                "firstname": "Shelley",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Capito",
+                "link": "https://www.govtrack.us/congress/members/shelley_capito/400061",
+                "middlename": "Moore",
+                "name": "Sen. Shelley Capito [R-WV]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00009771",
+                "pvsid": "11701",
+                "sortname": "Capito, Shelley (Sen.) [R-WV]",
+                "twitterid": "SenCapito",
+                "youtubeid": null
+            },
+            "phone": "202-224-6472",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "WV",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.capito.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Massachusetts",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "255 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.markey.senate.gov/contact",
+                "office": "255 Dirksen Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "M000133",
+                "birthday": "1946-07-11",
+                "cspanid": 260,
+                "firstname": "Edward",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Markey",
+                "link": "https://www.govtrack.us/congress/members/edward_markey/400253",
+                "middlename": "J.",
+                "name": "Sen. Edward “Ed” Markey [D-MA]",
+                "namemod": "",
+                "nickname": "Ed",
+                "osid": "N00000270",
+                "pvsid": "26900",
+                "sortname": "Markey, Edward “Ed” (Sen.) [D-MA]",
+                "twitterid": "SenMarkey",
+                "youtubeid": "RepMarkey"
+            },
+            "phone": "202-224-2742",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "MA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.markey.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for New Mexico",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "531 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.tomudall.senate.gov/?p=contact",
+                "fax": "202-228-3261",
+                "office": "531 Hart Senate Office Building",
+                "rss_url": "http://tomudall.senate.gov/rss/?p=blog"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "U000039",
+                "birthday": "1948-05-18",
+                "cspanid": 10075,
+                "firstname": "Tom",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Udall",
+                "link": "https://www.govtrack.us/congress/members/tom_udall/400413",
+                "middlename": "S.",
+                "name": "Sen. Tom Udall [D-NM]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00006561",
+                "pvsid": "22658",
+                "sortname": "Udall, Tom (Sen.) [D-NM]",
+                "twitterid": "SenatorTomUdall",
+                "youtubeid": "senatortomudall"
+            },
+            "phone": "202-224-6621",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "NM",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.tomudall.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Louisiana",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "520 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.cassidy.senate.gov/contact",
+                "fax": "202-225-7313",
+                "office": "520 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "C001075",
+                "birthday": "1957-09-28",
+                "cspanid": 1030546,
+                "firstname": "Bill",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Cassidy",
+                "link": "https://www.govtrack.us/congress/members/bill_cassidy/412269",
+                "middlename": "",
+                "name": "Sen. Bill Cassidy [R-LA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00030245",
+                "pvsid": "69494",
+                "sortname": "Cassidy, Bill (Sen.) [R-LA]",
+                "twitterid": null,
+                "youtubeid": "SenatorBillCassidy"
+            },
+            "phone": "202-224-5824",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "LA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.cassidy.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Michigan",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "724 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.peters.senate.gov/contact/email-gary",
+                "fax": "202-226-2356",
+                "office": "724 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "P000595",
+                "birthday": "1958-12-01",
+                "cspanid": 50199,
+                "firstname": "Gary",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Peters",
+                "link": "https://www.govtrack.us/congress/members/gary_peters/412305",
+                "middlename": "C.",
+                "name": "Sen. Gary Peters [D-MI]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00029277",
+                "pvsid": "8749",
+                "sortname": "Peters, Gary (Sen.) [D-MI]",
+                "twitterid": "SenGaryPeters",
+                "youtubeid": "RepGaryPeters"
+            },
+            "phone": "202-224-6221",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "MI",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.peters.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for Virginia",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "703 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.warner.senate.gov/public/index.cfm?p=Contact",
+                "fax": "202-224-6295",
+                "office": "703 Hart Senate Office Building",
+                "rss_url": "http://www.warner.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "W000805",
+                "birthday": "1954-12-15",
+                "cspanid": 7630,
+                "firstname": "Mark",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Warner",
+                "link": "https://www.govtrack.us/congress/members/mark_warner/412321",
+                "middlename": "",
+                "name": "Sen. Mark Warner [D-VA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00002097",
+                "pvsid": "535",
+                "sortname": "Warner, Mark (Sen.) [D-VA]",
+                "twitterid": "MarkWarner",
+                "youtubeid": "SenatorMarkWarner"
+            },
+            "phone": "202-224-2023",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "VA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.warner.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Idaho",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "483 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.risch.senate.gov/public/index.cfm?p=Email",
+                "fax": "202-224-2573",
+                "office": "483 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "R000584",
+                "birthday": "1943-05-03",
+                "cspanid": 1020034,
+                "firstname": "James",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Risch",
+                "link": "https://www.govtrack.us/congress/members/james_risch/412322",
+                "middlename": "",
+                "name": "Sen. James Risch [R-ID]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00029441",
+                "pvsid": "2919",
+                "sortname": "Risch, James (Sen.) [R-ID]",
+                "twitterid": "SenatorRisch",
+                "youtubeid": "SenatorJamesRisch"
+            },
+            "phone": "202-224-2752",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "ID",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.risch.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Senior Senator for New Hampshire",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "506 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.shaheen.senate.gov/contact/contact-jeanne",
+                "fax": "202-228-3194",
+                "office": "506 Hart Senate Office Building",
+                "rss_url": "http://www.shaheen.senate.gov/rss/"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "S001181",
+                "birthday": "1947-01-28",
+                "cspanid": 22850,
+                "firstname": "Jeanne",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Shaheen",
+                "link": "https://www.govtrack.us/congress/members/jeanne_shaheen/412323",
+                "middlename": "",
+                "name": "Sen. Jeanne Shaheen [D-NH]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00024790",
+                "pvsid": "1663",
+                "sortname": "Shaheen, Jeanne (Sen.) [D-NH]",
+                "twitterid": "SenatorShaheen",
+                "youtubeid": "senatorshaheen"
+            },
+            "phone": "202-224-2841",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2015-01-06",
+            "state": "NH",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.shaheen.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Oregon",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "313 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.merkley.senate.gov/contact/",
+                "fax": "202-228-3997",
+                "office": "313 Hart Senate Office Building",
+                "rss_url": "http://www.merkley.senate.gov/rss/"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "M001176",
+                "birthday": "1956-10-24",
+                "cspanid": 1029842,
+                "firstname": "Jeff",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Merkley",
+                "link": "https://www.govtrack.us/congress/members/jeff_merkley/412325",
+                "middlename": "",
+                "name": "Sen. Jeff Merkley [D-OR]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00029303",
+                "pvsid": "23644",
+                "sortname": "Merkley, Jeff (Sen.) [D-OR]",
+                "twitterid": "SenJeffMerkley",
+                "youtubeid": "SenatorJeffMerkley"
+            },
+            "phone": "202-224-3753",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "OR",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.merkley.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Delaware",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "127A Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.coons.senate.gov/contact",
+                "fax": "202-228-3075",
+                "office": "127a Russell Senate Office Building",
+                "rss_url": "http://www.coons.senate.gov/rss/feeds/?type=all"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "C001088",
+                "birthday": "1963-09-09",
+                "cspanid": 9269028,
+                "firstname": "Chris",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Coons",
+                "link": "https://www.govtrack.us/congress/members/chris_coons/412390",
+                "middlename": "Andrew",
+                "name": "Sen. Chris Coons [D-DE]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00031820",
+                "pvsid": "122834",
+                "sortname": "Coons, Chris (Sen.) [D-DE]",
+                "twitterid": "ChrisCoons",
+                "youtubeid": "senatorchriscoons"
+            },
+            "phone": "202-224-5042",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "DE",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.coons.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Colorado",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "354 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.gardner.senate.gov/contact-cory/email-cory",
+                "fax": "202-225-5870",
+                "office": "354 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "G000562",
+                "birthday": "1974-08-22",
+                "cspanid": 623308,
+                "firstname": "Cory",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Gardner",
+                "link": "https://www.govtrack.us/congress/members/cory_gardner/412406",
+                "middlename": "",
+                "name": "Sen. Cory Gardner [R-CO]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00030780",
+                "pvsid": "30004",
+                "sortname": "Gardner, Cory (Sen.) [R-CO]",
+                "twitterid": "SenCoryGardner",
+                "youtubeid": null
+            },
+            "phone": "202-224-5941",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "CO",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.gardner.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Arkansas",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "124 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.cotton.senate.gov/?p=contact",
+                "office": "124 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "C001095",
+                "birthday": "1977-05-13",
+                "cspanid": 63928,
+                "firstname": "Tom",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Cotton",
+                "link": "https://www.govtrack.us/congress/members/tom_cotton/412508",
+                "middlename": "",
+                "name": "Sen. Tom Cotton [R-AR]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00033363",
+                "pvsid": "135651",
+                "sortname": "Cotton, Tom (Sen.) [R-AR]",
+                "twitterid": "SenTomCotton",
+                "youtubeid": "RepTomCotton"
+            },
+            "phone": "202-224-2353",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "AR",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.cotton.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Montana",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "320 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.daines.senate.gov/connect/email-steve",
+                "fax": "202-228-1236",
+                "office": "320 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "D000618",
+                "birthday": "1962-08-20",
+                "cspanid": 1034037,
+                "firstname": "Steve",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Daines",
+                "link": "https://www.govtrack.us/congress/members/steve_daines/412549",
+                "middlename": "",
+                "name": "Sen. Steve Daines [R-MT]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00033054",
+                "pvsid": "135720",
+                "sortname": "Daines, Steve (Sen.) [R-MT]",
+                "twitterid": "SteveDaines",
+                "youtubeid": "SteveDainesMT"
+            },
+            "phone": "202-224-2651",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "MT",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.daines.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for New Jersey",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "359 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.booker.senate.gov/?p=contact",
+                "fax": "202-224-8378",
+                "office": "359 Dirksen Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "B001288",
+                "birthday": "1969-04-27",
+                "cspanid": 84679,
+                "firstname": "Cory",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Booker",
+                "link": "https://www.govtrack.us/congress/members/cory_booker/412598",
+                "middlename": "Anthony",
+                "name": "Sen. Cory Booker [D-NJ]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00035267",
+                "pvsid": "76151",
+                "sortname": "Booker, Cory (Sen.) [D-NJ]",
+                "twitterid": "SenBooker",
+                "youtubeid": "SenCoryBooker"
+            },
+            "phone": "202-224-3224",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "NJ",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.booker.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Alaska",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "702 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.sullivan.senate.gov/contact/email",
+                "office": "702 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "S001198",
+                "birthday": "1964-11-13",
+                "cspanid": 1023262,
+                "firstname": "Dan",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Sullivan",
+                "link": "https://www.govtrack.us/congress/members/dan_sullivan/412665",
+                "middlename": "",
+                "name": "Sen. Dan Sullivan [R-AK]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00035774",
+                "pvsid": "114964",
+                "sortname": "Sullivan, Dan (Sen.) [R-AK]",
+                "twitterid": "SenDanSullivan",
+                "youtubeid": null
+            },
+            "phone": "202-224-3004",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "AK",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.sullivan.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Georgia",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "455 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.perdue.senate.gov/connect/email",
+                "office": "455 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "P000612",
+                "birthday": "1949-12-10",
+                "cspanid": 75920,
+                "firstname": "David",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Perdue",
+                "link": "https://www.govtrack.us/congress/members/david_perdue/412666",
+                "middlename": "",
+                "name": "Sen. David Perdue [R-GA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00035516",
+                "pvsid": "151330",
+                "sortname": "Perdue, David (Sen.) [R-GA]",
+                "twitterid": "sendavidperdue",
+                "youtubeid": null
+            },
+            "phone": "202-224-3521",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "GA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.perdue.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Iowa",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "111 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.ernst.senate.gov/public/index.cfm/contact",
+                "office": "111 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "E000295",
+                "birthday": "1970-07-01",
+                "cspanid": 75342,
+                "firstname": "Joni",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Ernst",
+                "link": "https://www.govtrack.us/congress/members/joni_ernst/412667",
+                "middlename": "",
+                "name": "Sen. Joni Ernst [R-IA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00035483",
+                "pvsid": "128583",
+                "sortname": "Ernst, Joni (Sen.) [R-IA]",
+                "twitterid": "SenJoniErnst",
+                "youtubeid": null
+            },
+            "phone": "202-224-3254",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "IA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.ernst.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for North Carolina",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "185 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.tillis.senate.gov/public/index.cfm/email-me",
+                "office": "185 Dirksen Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "T000476",
+                "birthday": "1960-08-30",
+                "cspanid": 77055,
+                "firstname": "Thom",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Tillis",
+                "link": "https://www.govtrack.us/congress/members/thom_tillis/412668",
+                "middlename": "",
+                "name": "Sen. Thom Tillis [R-NC]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00035492",
+                "pvsid": "57717",
+                "sortname": "Tillis, Thom (Sen.) [R-NC]",
+                "twitterid": "senthomtillis",
+                "youtubeid": null
+            },
+            "phone": "202-224-6342",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "NC",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.tillis.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for South Dakota",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "502 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.rounds.senate.gov/contact/email-mike",
+                "office": "502 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "R000605",
+                "birthday": "1954-10-24",
+                "cspanid": 78317,
+                "firstname": "Mike",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Rounds",
+                "link": "https://www.govtrack.us/congress/members/mike_rounds/412669",
+                "middlename": "",
+                "name": "Sen. Mike Rounds [R-SD]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00035187",
+                "pvsid": "7455",
+                "sortname": "Rounds, Mike (Sen.) [R-SD]",
+                "twitterid": "SenatorRounds",
+                "youtubeid": null
+            },
+            "phone": "202-224-5842",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "SD",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.rounds.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                114,
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Nebraska",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "136 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.sasse.senate.gov/public/index.cfm/email-ben",
+                "office": "136 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "S001197",
+                "birthday": "1972-02-22",
+                "cspanid": 77429,
+                "firstname": "Benjamin",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Sasse",
+                "link": "https://www.govtrack.us/congress/members/benjamin_sasse/412671",
+                "middlename": "Eric",
+                "name": "Sen. Benjamin Sasse [R-NE]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00035544",
+                "pvsid": "150182",
+                "sortname": "Sasse, Benjamin (Sen.) [R-NE]",
+                "twitterid": "SenSasse",
+                "youtubeid": null
+            },
+            "phone": "202-224-4224",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2015-01-06",
+            "state": "NE",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.sasse.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Idaho",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "239 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.crapo.senate.gov/contact",
+                "fax": "202-228-1375",
+                "office": "239 Dirksen Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "C000880",
+                "birthday": "1951-05-20",
+                "cspanid": 26440,
+                "firstname": "Michael",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Crapo",
+                "link": "https://www.govtrack.us/congress/members/michael_crapo/300030",
+                "middlename": "D.",
+                "name": "Sen. Michael Crapo [R-ID]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00006267",
+                "pvsid": "26830",
+                "sortname": "Crapo, Michael (Sen.) [R-ID]",
+                "twitterid": "MikeCrapo",
+                "youtubeid": "senatorcrapo"
+            },
+            "phone": "202-224-6142",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "ID",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.crapo.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Iowa",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "135 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.grassley.senate.gov/contact",
+                "fax": "202-224-6020",
+                "office": "135 Hart Senate Office Building",
+                "rss_url": "http://grassley.senate.gov/customcf/rss_feed.cfm"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "G000386",
+                "birthday": "1933-09-17",
+                "cspanid": 1167,
+                "firstname": "Charles",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Grassley",
+                "link": "https://www.govtrack.us/congress/members/charles_grassley/300048",
+                "middlename": "E.",
+                "name": "Sen. Charles “Chuck” Grassley [R-IA]",
+                "namemod": "",
+                "nickname": "Chuck",
+                "osid": "N00001758",
+                "pvsid": "53293",
+                "sortname": "Grassley, Charles “Chuck” (Sen.) [R-IA]",
+                "twitterid": "ChuckGrassley",
+                "youtubeid": "senchuckgrassley"
+            },
+            "phone": "202-224-3744",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "IA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.grassley.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Vermont",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "437 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.leahy.senate.gov/contact/",
+                "fax": "202-224-3479",
+                "office": "437 Russell Senate Office Building",
+                "rss_url": "http://www.leahy.senate.gov/rss/feeds/press/"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "L000174",
+                "birthday": "1940-03-31",
+                "cspanid": 1552,
+                "firstname": "Patrick",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Leahy",
+                "link": "https://www.govtrack.us/congress/members/patrick_leahy/300065",
+                "middlename": "J.",
+                "name": "Sen. Patrick Leahy [D-VT]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00009918",
+                "pvsid": "53353",
+                "sortname": "Leahy, Patrick (Sen.) [D-VT]",
+                "twitterid": "SenatorLeahy",
+                "youtubeid": "SenatorPatrickLeahy"
+            },
+            "phone": "202-224-4242",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "VT",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.leahy.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Alaska",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "522 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.murkowski.senate.gov/public/index.cfm/contact",
+                "fax": "202-224-5301",
+                "office": "522 Hart Senate Office Building",
+                "rss_url": "http://www.murkowski.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "M001153",
+                "birthday": "1957-05-22",
+                "cspanid": 1004138,
+                "firstname": "Lisa",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Murkowski",
+                "link": "https://www.govtrack.us/congress/members/lisa_murkowski/300075",
+                "middlename": "A.",
+                "name": "Sen. Lisa Murkowski [R-AK]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00026050",
+                "pvsid": "15841",
+                "sortname": "Murkowski, Lisa (Sen.) [R-AK]",
+                "twitterid": "LisaMurkowski",
+                "youtubeid": "senatormurkowski"
+            },
+            "phone": "202-224-6665",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "AK",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.murkowski.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Washington",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "154 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.murray.senate.gov/public/index.cfm/contactme",
+                "fax": "202-224-0238",
+                "office": "154 Russell Senate Office Building",
+                "rss_url": "http://www.murray.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "M001111",
+                "birthday": "1950-10-11",
+                "cspanid": 25277,
+                "firstname": "Patty",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Murray",
+                "link": "https://www.govtrack.us/congress/members/patty_murray/300076",
+                "middlename": "",
+                "name": "Sen. Patty Murray [D-WA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00007876",
+                "pvsid": "53358",
+                "sortname": "Murray, Patty (Sen.) [D-WA]",
+                "twitterid": "PattyMurray",
+                "youtubeid": "SenatorPattyMurray"
+            },
+            "phone": "202-224-2621",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "WA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.murray.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for New York",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "322 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.schumer.senate.gov/contact/email-chuck",
+                "fax": "202-228-3027",
+                "office": "322 Hart Senate Office Building"
+            },
+            "leadership_title": "Minority Leader",
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "S000148",
+                "birthday": "1950-11-23",
+                "cspanid": 5929,
+                "firstname": "Charles",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Schumer",
+                "link": "https://www.govtrack.us/congress/members/charles_schumer/300087",
+                "middlename": "E.",
+                "name": "Sen. Charles “Chuck” Schumer [D-NY]",
+                "namemod": "",
+                "nickname": "Chuck",
+                "osid": "N00001093",
+                "pvsid": "26976",
+                "sortname": "Schumer, Charles “Chuck” (Sen.) [D-NY]",
+                "twitterid": "SenSchumer",
+                "youtubeid": "SenatorSchumer"
+            },
+            "phone": "202-224-6542",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "NY",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.schumer.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Alabama",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "304 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.shelby.senate.gov/public/index.cfm/emailsenatorshelby",
+                "fax": "202-224-3416",
+                "office": "304 Russell Senate Office Building",
+                "rss_url": "http://www.shelby.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "S000320",
+                "birthday": "1934-05-06",
+                "cspanid": 1859,
+                "firstname": "Richard",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Shelby",
+                "link": "https://www.govtrack.us/congress/members/richard_shelby/300089",
+                "middlename": "C.",
+                "name": "Sen. Richard Shelby [R-AL]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00009920",
+                "pvsid": "53266",
+                "sortname": "Shelby, Richard (Sen.) [R-AL]",
+                "twitterid": "SenShelby",
+                "youtubeid": "SenatorRichardShelby"
+            },
+            "phone": "202-224-5744",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "AL",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.shelby.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Oregon",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "221 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.wyden.senate.gov/contact/",
+                "fax": "202-228-2717",
+                "office": "221 Dirksen Senate Office Building",
+                "rss_url": "http://www.wyden.senate.gov/rss/feeds/?type=all&amp;"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "W000779",
+                "birthday": "1949-05-03",
+                "cspanid": 1962,
+                "firstname": "Ron",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Wyden",
+                "link": "https://www.govtrack.us/congress/members/ron_wyden/300100",
+                "middlename": "",
+                "name": "Sen. Ron Wyden [D-OR]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00007724",
+                "pvsid": "27036",
+                "sortname": "Wyden, Ron (Sen.) [D-OR]",
+                "twitterid": "RonWyden",
+                "youtubeid": "senronwyden"
+            },
+            "phone": "202-224-5244",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "OR",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.wyden.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Missouri",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "260 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.blunt.senate.gov/public/index.cfm/contact-roy",
+                "fax": "202-224-8149",
+                "office": "260 Russell Senate Office Building",
+                "rss_url": "http://www.blunt.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "B000575",
+                "birthday": "1950-01-10",
+                "cspanid": 45465,
+                "firstname": "Roy",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Blunt",
+                "link": "https://www.govtrack.us/congress/members/roy_blunt/400034",
+                "middlename": "",
+                "name": "Sen. Roy Blunt [R-MO]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00005195",
+                "pvsid": "418",
+                "sortname": "Blunt, Roy (Sen.) [R-MO]",
+                "twitterid": "RoyBlunt",
+                "youtubeid": "SenatorBlunt"
+            },
+            "phone": "202-224-5721",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "MO",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.blunt.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Arkansas",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "141 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.boozman.senate.gov/public/index.cfm/contact",
+                "fax": "202-228-1371",
+                "office": "141 Hart Senate Office Building",
+                "rss_url": "http://www.boozman.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "B001236",
+                "birthday": "1950-12-10",
+                "cspanid": 92069,
+                "firstname": "John",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Boozman",
+                "link": "https://www.govtrack.us/congress/members/john_boozman/400040",
+                "middlename": "",
+                "name": "Sen. John Boozman [R-AR]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00013873",
+                "pvsid": "27958",
+                "sortname": "Boozman, John (Sen.) [R-AR]",
+                "twitterid": "JohnBoozman",
+                "youtubeid": "BoozmanPressOffice"
+            },
+            "phone": "202-224-4843",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "AR",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.boozman.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for North Carolina",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "217 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.burr.senate.gov/contact/email",
+                "fax": "202-228-2981",
+                "office": "217 Russell Senate Office Building",
+                "rss_url": "http://www.burr.senate.gov/public/index.cfm?fuseaction=rss.feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "B001135",
+                "birthday": "1955-11-30",
+                "cspanid": 31054,
+                "firstname": "Richard",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Burr",
+                "link": "https://www.govtrack.us/congress/members/richard_burr/400054",
+                "middlename": "M.",
+                "name": "Sen. Richard Burr [R-NC]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00002221",
+                "pvsid": "21787",
+                "sortname": "Burr, Richard (Sen.) [R-NC]",
+                "twitterid": "SenatorBurr",
+                "youtubeid": "SenatorRichardBurr"
+            },
+            "phone": "202-224-3154",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "NC",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.burr.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Georgia",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "131 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.isakson.senate.gov/public/index.cfm/email-me",
+                "fax": "202-228-0724",
+                "office": "131 Russell Senate Office Building",
+                "rss_url": "http://www.isakson.senate.gov/public/?a=RSS.Feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "I000055",
+                "birthday": "1944-12-28",
+                "cspanid": 59135,
+                "firstname": "John",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Isakson",
+                "link": "https://www.govtrack.us/congress/members/john_isakson/400194",
+                "middlename": "H.",
+                "name": "Sen. John “Johnny” Isakson [R-GA]",
+                "namemod": "",
+                "nickname": "Johnny",
+                "osid": "N00002593",
+                "pvsid": "1721",
+                "sortname": "Isakson, John “Johnny” (Sen.) [R-GA]",
+                "twitterid": "SenatorIsakson",
+                "youtubeid": "SenatorIsakson"
+            },
+            "phone": "202-224-3643",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "GA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.isakson.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Kansas",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "521 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.moran.senate.gov/public/index.cfm/e-mail-jerry",
+                "fax": "202-228-6966",
+                "office": "521 Dirksen Senate Office Building",
+                "rss_url": "http://www.moran.senate.gov/public/index.cfm/rss/feed/"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "M000934",
+                "birthday": "1954-05-29",
+                "cspanid": 45469,
+                "firstname": "Jerry",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Moran",
+                "link": "https://www.govtrack.us/congress/members/jerry_moran/400284",
+                "middlename": "",
+                "name": "Sen. Jerry Moran [R-KS]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00005282",
+                "pvsid": "542",
+                "sortname": "Moran, Jerry (Sen.) [R-KS]",
+                "twitterid": "JerryMoran",
+                "youtubeid": "senatorjerrymoran"
+            },
+            "phone": "202-224-6521",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "KS",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.moran.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Ohio",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "448 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.portman.senate.gov/public/index.cfm/contact?p=contact-form",
+                "office": "448 Russell Senate Office Building",
+                "rss_url": "http://www.portman.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "P000449",
+                "birthday": "1955-12-19",
+                "cspanid": 31819,
+                "firstname": "Robert",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Portman",
+                "link": "https://www.govtrack.us/congress/members/robert_portman/400325",
+                "middlename": "J.",
+                "name": "Sen. Robert “Rob” Portman [R-OH]",
+                "namemod": "",
+                "nickname": "Rob",
+                "osid": "N00003682",
+                "pvsid": "27008",
+                "sortname": "Portman, Robert “Rob” (Sen.) [R-OH]",
+                "twitterid": "SenRobPortman",
+                "youtubeid": "SenRobPortman"
+            },
+            "phone": "202-224-3353",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "OH",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.portman.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Pennsylvania",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "248 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.toomey.senate.gov/?p=contact",
+                "fax": "202-228-0284",
+                "office": "248 Russell Senate Office Building",
+                "rss_url": "http://toomey.senate.gov/rss/?p=hot_topic"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "T000461",
+                "birthday": "1961-11-17",
+                "cspanid": 7958,
+                "firstname": "Patrick",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Toomey",
+                "link": "https://www.govtrack.us/congress/members/patrick_toomey/400408",
+                "middlename": "J.",
+                "name": "Sen. Patrick “Pat” Toomey [R-PA]",
+                "namemod": "",
+                "nickname": "Pat",
+                "osid": "N00001489",
+                "pvsid": "24096",
+                "sortname": "Toomey, Patrick “Pat” (Sen.) [R-PA]",
+                "twitterid": "SenToomey",
+                "youtubeid": "sentoomey"
+            },
+            "phone": "202-224-4254",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "PA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.toomey.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Maryland",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "110 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.vanhollen.senate.gov/contact/email",
+                "fax": "202-225-0375",
+                "office": "110 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "V000128",
+                "birthday": "1959-01-10",
+                "cspanid": 20756,
+                "firstname": "Chris",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Van Hollen",
+                "link": "https://www.govtrack.us/congress/members/chris_van_hollen/400415",
+                "middlename": "",
+                "name": "Sen. Chris Van Hollen [D-MD]",
+                "namemod": "Jr.",
+                "nickname": "",
+                "osid": "N00013820",
+                "pvsid": "6098",
+                "sortname": "Van Hollen, Chris (Sen.) [D-MD]",
+                "twitterid": "ChrisVanHollen",
+                "youtubeid": "RepChrisVanHollen"
+            },
+            "phone": "202-224-4654",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "MD",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.vanhollen.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for South Dakota",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "511 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.thune.senate.gov/public/index.cfm/contact",
+                "fax": "202-228-5429",
+                "office": "511 Dirksen Senate Office Building",
+                "rss_url": "http://www.thune.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "T000250",
+                "birthday": "1961-01-07",
+                "cspanid": 45552,
+                "firstname": "John",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Thune",
+                "link": "https://www.govtrack.us/congress/members/john_thune/400546",
+                "middlename": "",
+                "name": "Sen. John Thune [R-SD]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00004572",
+                "pvsid": "398",
+                "sortname": "Thune, John (Sen.) [R-SD]",
+                "twitterid": "SenJohnThune",
+                "youtubeid": "johnthune"
+            },
+            "phone": "202-224-2321",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "SD",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.thune.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Colorado",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "261 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.bennet.senate.gov/public/index.cfm/contact",
+                "fax": "202-228-5097",
+                "office": "261 Russell Senate Office Building",
+                "rss_url": "http://www.bennet.senate.gov/rss/feeds/?type=news"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "B001267",
+                "birthday": "1964-11-28",
+                "cspanid": 1031622,
+                "firstname": "Michael",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Bennet",
+                "link": "https://www.govtrack.us/congress/members/michael_bennet/412330",
+                "middlename": "F.",
+                "name": "Sen. Michael Bennet [D-CO]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00030608",
+                "pvsid": "110942",
+                "sortname": "Bennet, Michael (Sen.) [D-CO]",
+                "twitterid": "SenBennetCo",
+                "youtubeid": "SenatorBennet"
+            },
+            "phone": "202-224-5852",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "CO",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.bennet.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Indiana",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "400 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.young.senate.gov/contact",
+                "fax": "202-226-6866",
+                "office": "400 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "Y000064",
+                "birthday": "1972-08-24",
+                "cspanid": 1033743,
+                "firstname": "Todd",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Young",
+                "link": "https://www.govtrack.us/congress/members/todd_young/412428",
+                "middlename": "C.",
+                "name": "Sen. Todd Young [R-IN]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00030670",
+                "pvsid": "120345",
+                "sortname": "Young, Todd (Sen.) [R-IN]",
+                "twitterid": "SenToddYoung",
+                "youtubeid": "RepToddYoung"
+            },
+            "phone": "202-224-5623",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "IN",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.young.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Oklahoma",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "316 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.lankford.senate.gov/contact/email",
+                "office": "316 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "L000575",
+                "birthday": "1968-03-04",
+                "cspanid": 1033847,
+                "firstname": "James",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Lankford",
+                "link": "https://www.govtrack.us/congress/members/james_lankford/412464",
+                "middlename": "",
+                "name": "Sen. James Lankford [R-OK]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00031129",
+                "pvsid": "124938",
+                "sortname": "Lankford, James (Sen.) [R-OK]",
+                "twitterid": "SenatorLankford",
+                "youtubeid": "SenatorLankford"
+            },
+            "phone": "202-224-5754",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "OK",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.lankford.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for South Carolina",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "717 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.scott.senate.gov/contact/email-me",
+                "fax": "202-225-3407",
+                "office": "717 Hart Senate Office Building",
+                "rss_url": "http://www.scott.senate.gov/rss.xml"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "S001184",
+                "birthday": "1965-09-19",
+                "cspanid": 623506,
+                "firstname": "Tim",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Scott",
+                "link": "https://www.govtrack.us/congress/members/tim_scott/412471",
+                "middlename": "",
+                "name": "Sen. Tim Scott [R-SC]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00031782",
+                "pvsid": "11940",
+                "sortname": "Scott, Tim (Sen.) [R-SC]",
+                "twitterid": "SenatorTimScott",
+                "youtubeid": "SenatorTimScott"
+            },
+            "phone": "202-224-6121",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "SC",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.scott.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Connecticut",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "706 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.blumenthal.senate.gov/contact/",
+                "fax": "202-224-9673",
+                "office": "706 Hart Senate Office Building",
+                "rss_url": "http://www.blumenthal.senate.gov/rss/feeds/?type=all"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "B001277",
+                "birthday": "1946-02-13",
+                "cspanid": 21799,
+                "firstname": "Richard",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Blumenthal",
+                "link": "https://www.govtrack.us/congress/members/richard_blumenthal/412490",
+                "middlename": "",
+                "name": "Sen. Richard Blumenthal [D-CT]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00031685",
+                "pvsid": "1568",
+                "sortname": "Blumenthal, Richard (Sen.) [D-CT]",
+                "twitterid": "SenBlumenthal",
+                "youtubeid": "SenatorBlumenthal"
+            },
+            "phone": "202-224-2823",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "CT",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.blumenthal.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Florida",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "284 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.rubio.senate.gov/public/index.cfm/contact",
+                "fax": "202-228-0285",
+                "office": "284 Russell Senate Office Building",
+                "rss_url": "http://www.rubio.senate.gov/public/?a=rss.feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "R000595",
+                "birthday": "1971-05-28",
+                "cspanid": 87599,
+                "firstname": "Marco",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Rubio",
+                "link": "https://www.govtrack.us/congress/members/marco_rubio/412491",
+                "middlename": "",
+                "name": "Sen. Marco Rubio [R-FL]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00030612",
+                "pvsid": "1601",
+                "sortname": "Rubio, Marco (Sen.) [R-FL]",
+                "twitterid": "SenRubioPress",
+                "youtubeid": "SenatorMarcoRubio"
+            },
+            "phone": "202-224-3041",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "FL",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.rubio.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Kentucky",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "167 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.paul.senate.gov/connect/email-rand",
+                "fax": "202-228-1373",
+                "office": "167 Russell Senate Office Building",
+                "rss_url": "http://paul.senate.gov/rss/?p=news"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "P000603",
+                "birthday": "1963-01-07",
+                "cspanid": 9265241,
+                "firstname": "Rand",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Paul",
+                "link": "https://www.govtrack.us/congress/members/rand_paul/412492",
+                "middlename": "",
+                "name": "Sen. Rand Paul [R-KY]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00030836",
+                "pvsid": "117285",
+                "sortname": "Paul, Rand (Sen.) [R-KY]",
+                "twitterid": "RandPaul",
+                "youtubeid": "SenatorRandPaul"
+            },
+            "phone": "202-224-4343",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "KY",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.paul.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for North Dakota",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "338 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "http://www.hoeven.senate.gov/public/index.cfm/email-the-senator",
+                "fax": "202-224-7999",
+                "office": "338 Russell Senate Office Building",
+                "rss_url": "http://www.hoeven.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "H001061",
+                "birthday": "1957-03-13",
+                "cspanid": 85233,
+                "firstname": "John",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Hoeven",
+                "link": "https://www.govtrack.us/congress/members/john_hoeven/412494",
+                "middlename": "",
+                "name": "Sen. John Hoeven [R-ND]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00031688",
+                "pvsid": "41788",
+                "sortname": "Hoeven, John (Sen.) [R-ND]",
+                "twitterid": "SenJohnHoeven",
+                "youtubeid": "senatorjohnhoevennd"
+            },
+            "phone": "202-224-2551",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "ND",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.hoeven.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Utah",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "361A Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.lee.senate.gov/public/index.cfm/contact",
+                "office": "361a Russell Senate Office Building",
+                "rss_url": "http://www.lee.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "L000577",
+                "birthday": "1971-06-04",
+                "cspanid": 9267977,
+                "firstname": "Mike",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Lee",
+                "link": "https://www.govtrack.us/congress/members/mike_lee/412495",
+                "middlename": "",
+                "name": "Sen. Mike Lee [R-UT]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00031696",
+                "pvsid": "66395",
+                "sortname": "Lee, Mike (Sen.) [R-UT]",
+                "twitterid": "SenMikeLee",
+                "youtubeid": "senatormikelee"
+            },
+            "phone": "202-224-5444",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "UT",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.lee.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Wisconsin",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "328 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.ronjohnson.senate.gov/public/index.cfm/email-the-senator",
+                "fax": "920-230-7262",
+                "office": "328 Hart Senate Office Building",
+                "rss_url": "http://www.ronjohnson.senate.gov/public/index.cfm/rss/feed"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "J000293",
+                "birthday": "1955-04-08",
+                "cspanid": 62835,
+                "firstname": "Ron",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Johnson",
+                "link": "https://www.govtrack.us/congress/members/ron_johnson/412496",
+                "middlename": "",
+                "name": "Sen. Ron Johnson [R-WI]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00032546",
+                "pvsid": "126217",
+                "sortname": "Johnson, Ron (Sen.) [R-WI]",
+                "twitterid": "SenRonJohnson",
+                "youtubeid": "SenatorRonJohnson"
+            },
+            "phone": "202-224-5323",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "WI",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.ronjohnson.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Senior Senator for Hawaii",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "722 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.schatz.senate.gov/contact",
+                "fax": "202-228-1153",
+                "office": "722 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "S001194",
+                "birthday": "1972-10-20",
+                "cspanid": 87784,
+                "firstname": "Brian",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Schatz",
+                "link": "https://www.govtrack.us/congress/members/brian_schatz/412507",
+                "middlename": "Emanuel",
+                "name": "Sen. Brian Schatz [D-HI]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00028138",
+                "pvsid": "17852",
+                "sortname": "Schatz, Brian (Sen.) [D-HI]",
+                "twitterid": "SenBrianSchatz",
+                "youtubeid": "senbrianschatz"
+            },
+            "phone": "202-224-3934",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "senior",
+            "senator_rank_label": "Senior",
+            "startdate": "2017-01-03",
+            "state": "HI",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.schatz.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Illinois",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "524 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.duckworth.senate.gov/content/contact-senator",
+                "office": "524 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "D000622",
+                "birthday": "1968-03-12",
+                "cspanid": 94484,
+                "firstname": "Tammy",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Duckworth",
+                "link": "https://www.govtrack.us/congress/members/tammy_duckworth/412533",
+                "middlename": "",
+                "name": "Sen. Tammy Duckworth [D-IL]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00027860",
+                "pvsid": "57442",
+                "sortname": "Duckworth, Tammy (Sen.) [D-IL]",
+                "twitterid": "SenDuckworth",
+                "youtubeid": "repduckworth"
+            },
+            "phone": "202-224-2854",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "IL",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.duckworth.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for California",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "112 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.harris.senate.gov/contact",
+                "office": "112 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "H001075",
+                "birthday": "1964-10-20",
+                "cspanid": 1018696,
+                "firstname": "Kamala",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Harris",
+                "link": "https://www.govtrack.us/congress/members/kamala_harris/412678",
+                "middlename": "",
+                "name": "Sen. Kamala Harris [D-CA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00036915",
+                "pvsid": "120012",
+                "sortname": "Harris, Kamala (Sen.) [D-CA]",
+                "twitterid": "SenKamalaHarris",
+                "youtubeid": null
+            },
+            "phone": "202-224-3553",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "CA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.harris.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Louisiana",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "383 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.kennedy.senate.gov/public/email-me",
+                "office": "383 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "K000393",
+                "birthday": "1951-11-21",
+                "cspanid": 1011723,
+                "firstname": "John",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Kennedy",
+                "link": "https://www.govtrack.us/congress/members/john_kennedy/412679",
+                "middlename": "Neely",
+                "name": "Sen. John Kennedy [R-LA]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00026823",
+                "pvsid": "35496",
+                "sortname": "Kennedy, John (Sen.) [R-LA]",
+                "twitterid": "SenJohnKennedy",
+                "youtubeid": null
+            },
+            "phone": "202-224-4623",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "LA",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.kennedy.senate.gov/public"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for New Hampshire",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "330 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.hassan.senate.gov/content/contact-senator",
+                "office": "330 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "H001076",
+                "birthday": "1958-02-27",
+                "cspanid": 67481,
+                "firstname": "Margaret",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Hassan",
+                "link": "https://www.govtrack.us/congress/members/margaret_hassan/412680",
+                "middlename": "Wood",
+                "name": "Sen. Margaret “Maggie” Hassan [D-NH]",
+                "namemod": "",
+                "nickname": "Maggie",
+                "osid": "N00038397",
+                "pvsid": "42552",
+                "sortname": "Hassan, Margaret “Maggie” (Sen.) [D-NH]",
+                "twitterid": "Senatorhassan",
+                "youtubeid": null
+            },
+            "phone": "202-224-3324",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "NH",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.hassan.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116,
+                117
+            ],
+            "current": true,
+            "description": "Junior Senator for Nevada",
+            "district": null,
+            "enddate": "2023-01-03",
+            "extra": {
+                "address": "204 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.cortezmasto.senate.gov/contact",
+                "office": "204 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "C001113",
+                "birthday": "1964-03-29",
+                "cspanid": 105698,
+                "firstname": "Catherine",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Cortez Masto",
+                "link": "https://www.govtrack.us/congress/members/catherine_cortez_masto/412681",
+                "middlename": "",
+                "name": "Sen. Catherine Cortez Masto [D-NV]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00037161",
+                "pvsid": "69579",
+                "sortname": "Cortez Masto, Catherine (Sen.) [D-NV]",
+                "twitterid": "sencortezmasto",
+                "youtubeid": null
+            },
+            "phone": "202-224-3542",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2017-01-03",
+            "state": "NV",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.cortezmasto.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Alabama",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "326 Russell Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.jones.senate.gov/content/contact-senator",
+                "office": "326 Russell Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "J000300",
+                "birthday": "1954-05-04",
+                "cspanid": 1024592,
+                "firstname": "Doug",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Jones",
+                "link": "https://www.govtrack.us/congress/members/doug_jones/412741",
+                "middlename": "",
+                "name": "Sen. Doug Jones [D-AL]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00024817",
+                "pvsid": "176464",
+                "sortname": "Jones, Doug (Sen.) [D-AL]",
+                "twitterid": "sendougjones",
+                "youtubeid": null
+            },
+            "phone": "202-224-4124",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2018-01-03",
+            "state": "AL",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.jones.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115
+            ],
+            "current": true,
+            "description": "Junior Senator for Mississippi",
+            "district": null,
+            "enddate": "2018-11-27",
+            "extra": {
+                "address": "G12 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.hydesmith.senate.gov/content/contact-senator",
+                "end-type": "special-election",
+                "how": "appointment",
+                "office": "G12 Dirksen Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "H001079",
+                "birthday": "1959-05-10",
+                "cspanid": null,
+                "firstname": "Cindy",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Hyde-Smith",
+                "link": "https://www.govtrack.us/congress/members/cindy_hyde_smith/412743",
+                "middlename": "",
+                "name": "Sen. Cindy Hyde-Smith [R-MS]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00043298",
+                "pvsid": null,
+                "sortname": "Hyde-Smith, Cindy (Sen.) [R-MS]",
+                "twitterid": "SenHydeSmith",
+                "youtubeid": null
+            },
+            "phone": "202-224-5054",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2018-04-09",
+            "state": "MS",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.hydesmith.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Arizona",
+            "district": null,
+            "enddate": "2020-11-03",
+            "extra": {
+                "address": "G12 Dirksen Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.kyl.senate.gov/content/contact-senator-kyl",
+                "end-type": "special-election",
+                "fax": "202-228-2862",
+                "how": "appointment",
+                "office": "G12 Dirksen Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Republican",
+            "person": {
+                "bioguideid": "K000352",
+                "birthday": "1942-04-25",
+                "cspanid": null,
+                "firstname": "Jon",
+                "gender": "male",
+                "gender_label": "Male",
+                "lastname": "Kyl",
+                "link": "https://www.govtrack.us/congress/members/jon_kyl/300062",
+                "middlename": "",
+                "name": "Sen. Jon Kyl [R-AZ]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00006406",
+                "pvsid": "26721",
+                "sortname": "Kyl, Jon (Sen.) [R-AZ]",
+                "twitterid": null,
+                "youtubeid": null
+            },
+            "phone": "202-224-2235",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class3",
+            "senator_class_label": "Class 3",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2018-09-05",
+            "state": "AZ",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.kyl.senate.gov"
+        },
+        {
+            "caucus": null,
+            "congress_numbers": [
+                115,
+                116
+            ],
+            "current": true,
+            "description": "Junior Senator for Minnesota",
+            "district": null,
+            "enddate": "2021-01-03",
+            "extra": {
+                "address": "309 Hart Senate Office Building Washington DC 20510",
+                "contact_form": "https://www.smith.senate.gov/content/contact-senator",
+                "office": "309 Hart Senate Office Building"
+            },
+            "leadership_title": null,
+            "party": "Democrat",
+            "person": {
+                "bioguideid": "S001203",
+                "birthday": "1958-03-04",
+                "cspanid": 111313,
+                "firstname": "Tina",
+                "gender": "female",
+                "gender_label": "Female",
+                "lastname": "Smith",
+                "link": "https://www.govtrack.us/congress/members/tina_smith/412742",
+                "middlename": "Flint",
+                "name": "Sen. Tina Smith [D-MN]",
+                "namemod": "",
+                "nickname": "",
+                "osid": "N00042353",
+                "pvsid": "152968",
+                "sortname": "Smith, Tina (Sen.) [D-MN]",
+                "twitterid": "SenTinaSmith",
+                "youtubeid": null
+            },
+            "phone": "202-224-5641",
+            "role_type": "senator",
+            "role_type_label": "Senator",
+            "senator_class": "class2",
+            "senator_class_label": "Class 2",
+            "senator_rank": "junior",
+            "senator_rank_label": "Junior",
+            "startdate": "2018-11-07",
+            "state": "MN",
+            "title": "Sen.",
+            "title_long": "Senator",
+            "website": "https://www.smith.senate.gov"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sample_json_lines.json b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sample_json_lines.json
new file mode 100644 (file)
index 0000000..1cecaee
--- /dev/null
@@ -0,0 +1,5145 @@
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Wisconsin",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "709 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.baldwin.senate.gov/feedback",
+        "fax": "202-225-6942",
+        "office": "709 Hart Senate Office Building",
+        "rss_url": "http://www.baldwin.senate.gov/rss/feeds/?type=all"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "B001230",
+        "birthday": "1962-02-11",
+        "cspanid": 57884,
+        "firstname": "Tammy",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Baldwin",
+        "link": "https://www.govtrack.us/congress/members/tammy_baldwin/400013",
+        "middlename": "",
+        "name": "Sen. Tammy Baldwin [D-WI]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00004367",
+        "pvsid": "3470",
+        "sortname": "Baldwin, Tammy (Sen.) [D-WI]",
+        "twitterid": "SenatorBaldwin",
+        "youtubeid": "witammybaldwin"
+    },
+    "phone": "202-224-5653",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "WI",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.baldwin.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Ohio",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "713 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.brown.senate.gov/contact/",
+        "fax": "202-228-6321",
+        "office": "713 Hart Senate Office Building",
+        "rss_url": "http://www.brown.senate.gov/rss/feeds/?type=all&amp;"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "B000944",
+        "birthday": "1952-11-09",
+        "cspanid": 5051,
+        "firstname": "Sherrod",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Brown",
+        "link": "https://www.govtrack.us/congress/members/sherrod_brown/400050",
+        "middlename": "",
+        "name": "Sen. Sherrod Brown [D-OH]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00003535",
+        "pvsid": "27018",
+        "sortname": "Brown, Sherrod (Sen.) [D-OH]",
+        "twitterid": "SenSherrodBrown",
+        "youtubeid": "SherrodBrownOhio"
+    },
+    "phone": "202-224-2315",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "OH",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.brown.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Maryland",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "509 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.cardin.senate.gov/contact/",
+        "fax": "202-224-1651",
+        "office": "509 Hart Senate Office Building",
+        "rss_url": "http://www.cardin.senate.gov/rss/feeds/?type=all"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "C000141",
+        "birthday": "1943-10-05",
+        "cspanid": 4004,
+        "firstname": "Benjamin",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Cardin",
+        "link": "https://www.govtrack.us/congress/members/benjamin_cardin/400064",
+        "middlename": "L.",
+        "name": "Sen. Benjamin Cardin [D-MD]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00001955",
+        "pvsid": "26888",
+        "sortname": "Cardin, Benjamin (Sen.) [D-MD]",
+        "twitterid": "SenatorCardin",
+        "youtubeid": "senatorcardin"
+    },
+    "phone": "202-224-4524",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "MD",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.cardin.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Arizona",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "413 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.flake.senate.gov/public/index.cfm/contact-jeff",
+        "fax": "202-226-4386",
+        "office": "413 Russell Senate Office Building",
+        "rss_url": "http://www.flake.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "F000444",
+        "birthday": "1962-12-31",
+        "cspanid": 87582,
+        "firstname": "Jeff",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Flake",
+        "link": "https://www.govtrack.us/congress/members/jeff_flake/400134",
+        "middlename": "",
+        "name": "Sen. Jeff Flake [R-AZ]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00009573",
+        "pvsid": "28128",
+        "sortname": "Flake, Jeff (Sen.) [R-AZ]",
+        "twitterid": "JeffFlake",
+        "youtubeid": "flakeoffice"
+    },
+    "phone": "202-224-4521",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "AZ",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.flake.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for New Jersey",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "528 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.menendez.senate.gov/contact",
+        "fax": "202-228-2197",
+        "office": "528 Hart Senate Office Building",
+        "rss_url": "http://www.menendez.senate.gov/rss/feeds/index.cfm?type=news"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "M000639",
+        "birthday": "1954-01-01",
+        "cspanid": 29608,
+        "firstname": "Robert",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Menéndez",
+        "link": "https://www.govtrack.us/congress/members/robert_menendez/400272",
+        "middlename": "",
+        "name": "Sen. Robert “Bob” Menéndez [D-NJ]",
+        "namemod": "",
+        "nickname": "Bob",
+        "osid": "N00000699",
+        "pvsid": "26961",
+        "sortname": "Menéndez, Robert “Bob” (Sen.) [D-NJ]",
+        "twitterid": "SenatorMenendez",
+        "youtubeid": "SenatorMenendezNJ"
+    },
+    "phone": "202-224-4744",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "NJ",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.menendez.senate.gov"
+},
+{
+    "caucus": "Democrat",
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Vermont",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "332 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.sanders.senate.gov/contact/",
+        "fax": "202-228-0776",
+        "office": "332 Dirksen Senate Office Building",
+        "rss_url": "http://www.sanders.senate.gov/rss/"
+    },
+    "leadership_title": null,
+    "party": "Independent",
+    "person": {
+        "bioguideid": "S000033",
+        "birthday": "1941-09-08",
+        "cspanid": 994,
+        "firstname": "Bernard",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Sanders",
+        "link": "https://www.govtrack.us/congress/members/bernard_sanders/400357",
+        "middlename": "",
+        "name": "Sen. Bernard “Bernie” Sanders [I-VT]",
+        "namemod": "",
+        "nickname": "Bernie",
+        "osid": "N00000528",
+        "pvsid": "27110",
+        "sortname": "Sanders, Bernard “Bernie” (Sen.) [I-VT]",
+        "twitterid": "SenSanders",
+        "youtubeid": "senatorsanders"
+    },
+    "phone": "202-224-5141",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "VT",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.sanders.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Washington",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "511 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.cantwell.senate.gov/public/index.cfm/email-maria",
+        "fax": "202-228-0514",
+        "office": "511 Hart Senate Office Building",
+        "rss_url": "http://www.cantwell.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "C000127",
+        "birthday": "1958-10-13",
+        "cspanid": 26137,
+        "firstname": "Maria",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Cantwell",
+        "link": "https://www.govtrack.us/congress/members/maria_cantwell/300018",
+        "middlename": "",
+        "name": "Sen. Maria Cantwell [D-WA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00007836",
+        "pvsid": "27122",
+        "sortname": "Cantwell, Maria (Sen.) [D-WA]",
+        "twitterid": "SenatorCantwell",
+        "youtubeid": "SenatorCantwell"
+    },
+    "phone": "202-224-3441",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "WA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.cantwell.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Delaware",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "513 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.carper.senate.gov/public/index.cfm/email-senator-carper",
+        "fax": "202-228-2190",
+        "office": "513 Hart Senate Office Building",
+        "rss_url": "http://www.carper.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "C000174",
+        "birthday": "1947-01-23",
+        "cspanid": 663,
+        "firstname": "Thomas",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Carper",
+        "link": "https://www.govtrack.us/congress/members/thomas_carper/300019",
+        "middlename": "Richard",
+        "name": "Sen. Thomas Carper [D-DE]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00012508",
+        "pvsid": "22421",
+        "sortname": "Carper, Thomas (Sen.) [D-DE]",
+        "twitterid": "SenatorCarper",
+        "youtubeid": "senatorcarper"
+    },
+    "phone": "202-224-2441",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "DE",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.carper.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for California",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "331 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.feinstein.senate.gov/public/index.cfm/e-mail-me",
+        "fax": "202-228-3954",
+        "office": "331 Hart Senate Office Building",
+        "rss_url": "http://www.feinstein.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "F000062",
+        "birthday": "1933-06-22",
+        "cspanid": 13061,
+        "firstname": "Dianne",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Feinstein",
+        "link": "https://www.govtrack.us/congress/members/dianne_feinstein/300043",
+        "middlename": "",
+        "name": "Sen. Dianne Feinstein [D-CA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00007364",
+        "pvsid": "53273",
+        "sortname": "Feinstein, Dianne (Sen.) [D-CA]",
+        "twitterid": "SenFeinstein",
+        "youtubeid": "SenatorFeinstein"
+    },
+    "phone": "202-224-3841",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "CA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.feinstein.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Utah",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "104 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.hatch.senate.gov/public/index.cfm/contact?p=Email-Orrin",
+        "fax": "202-224-6331",
+        "office": "104 Hart Senate Office Building",
+        "rss_url": "http://www.hatch.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "H000338",
+        "birthday": "1934-03-22",
+        "cspanid": 189,
+        "firstname": "Orrin",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Hatch",
+        "link": "https://www.govtrack.us/congress/members/orrin_hatch/300052",
+        "middlename": "G.",
+        "name": "Sen. Orrin Hatch [R-UT]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00009869",
+        "pvsid": "53352",
+        "sortname": "Hatch, Orrin (Sen.) [R-UT]",
+        "twitterid": "SenOrrinHatch",
+        "youtubeid": "SenatorOrrinHatch"
+    },
+    "phone": "202-224-5251",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "UT",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.hatch.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Florida",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "716 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.billnelson.senate.gov/contact-bill",
+        "fax": "202-228-2183",
+        "office": "716 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "N000032",
+        "birthday": "1942-09-29",
+        "cspanid": 1931,
+        "firstname": "Bill",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Nelson",
+        "link": "https://www.govtrack.us/congress/members/bill_nelson/300078",
+        "middlename": "",
+        "name": "Sen. Bill Nelson [D-FL]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00009926",
+        "pvsid": "1606",
+        "sortname": "Nelson, Bill (Sen.) [D-FL]",
+        "twitterid": "SenBillNelson",
+        "youtubeid": "senbillnelson"
+    },
+    "phone": "202-224-5274",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "FL",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.billnelson.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Michigan",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "731 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.stabenow.senate.gov/contact",
+        "fax": "202-228-0325",
+        "office": "731 Hart Senate Office Building",
+        "rss_url": "http://stabenow.senate.gov/rss/?p=news"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "S000770",
+        "birthday": "1950-04-29",
+        "cspanid": 45451,
+        "firstname": "Debbie",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Stabenow",
+        "link": "https://www.govtrack.us/congress/members/debbie_stabenow/300093",
+        "middlename": "Ann",
+        "name": "Sen. Debbie Stabenow [D-MI]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00004118",
+        "pvsid": "515",
+        "sortname": "Stabenow, Debbie (Sen.) [D-MI]",
+        "twitterid": "SenStabenow",
+        "youtubeid": "senatorstabenow"
+    },
+    "phone": "202-224-4822",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "MI",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.stabenow.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Connecticut",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "136 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.murphy.senate.gov/contact",
+        "fax": "202-225-5933",
+        "office": "136 Hart Senate Office Building",
+        "rss_url": "http://www.theday.com/article/20121216/nws12/312169935/1069/rss"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "M001169",
+        "birthday": "1973-08-03",
+        "cspanid": 1021270,
+        "firstname": "Christopher",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Murphy",
+        "link": "https://www.govtrack.us/congress/members/christopher_murphy/412194",
+        "middlename": "S.",
+        "name": "Sen. Christopher Murphy [D-CT]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00027566",
+        "pvsid": "17189",
+        "sortname": "Murphy, Christopher (Sen.) [D-CT]",
+        "twitterid": "senmurphyoffice",
+        "youtubeid": "senchrismurphy"
+    },
+    "phone": "202-224-4041",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "CT",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.murphy.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Hawaii",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "730 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.hirono.senate.gov/contact",
+        "fax": "202-225-4987",
+        "office": "730 Hart Senate Office Building",
+        "rss_url": "http://www.hirono.senate.gov/rss/feeds/?type=all"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "H001042",
+        "birthday": "1947-11-03",
+        "cspanid": 91216,
+        "firstname": "Mazie",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Hirono",
+        "link": "https://www.govtrack.us/congress/members/mazie_hirono/412200",
+        "middlename": "K.",
+        "name": "Sen. Mazie Hirono [D-HI]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00028139",
+        "pvsid": "1677",
+        "sortname": "Hirono, Mazie (Sen.) [D-HI]",
+        "twitterid": "MazieHirono",
+        "youtubeid": "CongresswomanHirono"
+    },
+    "phone": "202-224-6361",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "HI",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.hirono.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Indiana",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "720 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.donnelly.senate.gov/contact/email-joe",
+        "fax": "202-225-6798",
+        "office": "720 Hart Senate Office Building",
+        "rss_url": "http://www.donnelly.senate.gov/rss/feeds/?type=all"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "D000607",
+        "birthday": "1955-09-28",
+        "cspanid": 1012000,
+        "firstname": "Joe",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Donnelly",
+        "link": "https://www.govtrack.us/congress/members/joe_donnelly/412205",
+        "middlename": "",
+        "name": "Sen. Joe Donnelly [D-IN]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00026586",
+        "pvsid": "34212",
+        "sortname": "Donnelly, Joe (Sen.) [D-IN]",
+        "twitterid": "SenDonnelly",
+        "youtubeid": "sendonnelly"
+    },
+    "phone": "202-224-4814",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "IN",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.donnelly.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Nevada",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "324 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.heller.senate.gov/public/index.cfm/contact-form",
+        "fax": "202-228-6753",
+        "office": "324 Hart Senate Office Building",
+        "rss_url": "http://www.heller.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "H001041",
+        "birthday": "1960-05-10",
+        "cspanid": 1012368,
+        "firstname": "Dean",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Heller",
+        "link": "https://www.govtrack.us/congress/members/dean_heller/412218",
+        "middlename": "",
+        "name": "Sen. Dean Heller [R-NV]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00027522",
+        "pvsid": "2291",
+        "sortname": "Heller, Dean (Sen.) [R-NV]",
+        "twitterid": "SenDeanHeller",
+        "youtubeid": "SenDeanHeller"
+    },
+    "phone": "202-224-6244",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "NV",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.heller.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for New York",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "478 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.gillibrand.senate.gov/contact/email-me",
+        "fax": "202-228-0282",
+        "office": "478 Russell Senate Office Building",
+        "rss_url": "http://www.gillibrand.senate.gov/rss/"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "G000555",
+        "birthday": "1966-12-09",
+        "cspanid": 1022862,
+        "firstname": "Kirsten",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Gillibrand",
+        "link": "https://www.govtrack.us/congress/members/kirsten_gillibrand/412223",
+        "middlename": "E.",
+        "name": "Sen. Kirsten Gillibrand [D-NY]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00027658",
+        "pvsid": "65147",
+        "sortname": "Gillibrand, Kirsten (Sen.) [D-NY]",
+        "twitterid": "SenGillibrand",
+        "youtubeid": "KirstenEGillibrand"
+    },
+    "phone": "202-224-4451",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "NY",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.gillibrand.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Minnesota",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "302 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.klobuchar.senate.gov/public/index.cfm/contact",
+        "fax": "202-228-2186",
+        "office": "302 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "K000367",
+        "birthday": "1960-05-25",
+        "cspanid": 83701,
+        "firstname": "Amy",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Klobuchar",
+        "link": "https://www.govtrack.us/congress/members/amy_klobuchar/412242",
+        "middlename": "Jean",
+        "name": "Sen. Amy Klobuchar [D-MN]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00027500",
+        "pvsid": "65092",
+        "sortname": "Klobuchar, Amy (Sen.) [D-MN]",
+        "twitterid": "SenAmyKlobuchar",
+        "youtubeid": "senatorklobuchar"
+    },
+    "phone": "202-224-3244",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "MN",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.klobuchar.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Missouri",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "503 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.mccaskill.senate.gov/contact",
+        "fax": "202-228-6326",
+        "office": "503 Hart Senate Office Building",
+        "rss_url": "http://mccaskill.senate.gov/rss/?p=news"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "M001170",
+        "birthday": "1953-07-24",
+        "cspanid": 44501,
+        "firstname": "Claire",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "McCaskill",
+        "link": "https://www.govtrack.us/congress/members/claire_mccaskill/412243",
+        "middlename": "",
+        "name": "Sen. Claire McCaskill [D-MO]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00027694",
+        "pvsid": "2109",
+        "sortname": "McCaskill, Claire (Sen.) [D-MO]",
+        "twitterid": "McCaskillOffice",
+        "youtubeid": "SenatorMcCaskill"
+    },
+    "phone": "202-224-6154",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "MO",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.mccaskill.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Montana",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "311 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.tester.senate.gov/?p=email_senator",
+        "fax": "202-224-8594",
+        "office": "311 Hart Senate Office Building",
+        "rss_url": "http://www.tester.senate.gov/rss/?p=hot_topic"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "T000464",
+        "birthday": "1956-08-21",
+        "cspanid": 1020176,
+        "firstname": "Jon",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Tester",
+        "link": "https://www.govtrack.us/congress/members/jon_tester/412244",
+        "middlename": "",
+        "name": "Sen. Jon Tester [D-MT]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00027605",
+        "pvsid": "20928",
+        "sortname": "Tester, Jon (Sen.) [D-MT]",
+        "twitterid": "SenatorTester",
+        "youtubeid": "senatorjontester"
+    },
+    "phone": "202-224-2644",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "MT",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.tester.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Pennsylvania",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "393 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.casey.senate.gov/contact/",
+        "fax": "202-228-0604",
+        "office": "393 Russell Senate Office Building",
+        "rss_url": "http://www.casey.senate.gov/rss/feeds/?all"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "C001070",
+        "birthday": "1960-04-13",
+        "cspanid": 47036,
+        "firstname": "Robert",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Casey",
+        "link": "https://www.govtrack.us/congress/members/robert_casey/412246",
+        "middlename": "P.",
+        "name": "Sen. Robert “Bob” Casey [D-PA]",
+        "namemod": "Jr.",
+        "nickname": "Bob",
+        "osid": "N00027503",
+        "pvsid": "2541",
+        "sortname": "Casey, Robert “Bob” (Sen.) [D-PA]",
+        "twitterid": "SenBobCasey",
+        "youtubeid": "SenatorBobCasey"
+    },
+    "phone": "202-224-6324",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "PA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.casey.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Rhode Island",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "530 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.whitehouse.senate.gov/contact/email-sheldon",
+        "fax": "202-228-6362",
+        "office": "530 Hart Senate Office Building",
+        "rss_url": "http://www.whitehouse.senate.gov/rss/feeds/?type=all&amp;cachebuster=1"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "W000802",
+        "birthday": "1955-10-20",
+        "cspanid": 92235,
+        "firstname": "Sheldon",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Whitehouse",
+        "link": "https://www.govtrack.us/congress/members/sheldon_whitehouse/412247",
+        "middlename": "",
+        "name": "Sen. Sheldon Whitehouse [D-RI]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00027533",
+        "pvsid": "2572",
+        "sortname": "Whitehouse, Sheldon (Sen.) [D-RI]",
+        "twitterid": "SenWhitehouse",
+        "youtubeid": "SenatorWhitehouse"
+    },
+    "phone": "202-224-2921",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "RI",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.whitehouse.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Tennessee",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "425 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.corker.senate.gov/public/index.cfm/emailme",
+        "fax": "202-228-0566",
+        "office": "425 Dirksen Senate Office Building",
+        "rss_url": "http://www.corker.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "C001071",
+        "birthday": "1952-08-24",
+        "cspanid": 1021114,
+        "firstname": "Bob",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Corker",
+        "link": "https://www.govtrack.us/congress/members/bob_corker/412248",
+        "middlename": "",
+        "name": "Sen. Bob Corker [R-TN]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00027441",
+        "pvsid": "65905",
+        "sortname": "Corker, Bob (Sen.) [R-TN]",
+        "twitterid": "SenBobCorker",
+        "youtubeid": "senatorcorker"
+    },
+    "phone": "202-224-3344",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "TN",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.corker.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Wyoming",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "307 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.barrasso.senate.gov/public/index.cfm/contact-form",
+        "fax": "202-224-1724",
+        "office": "307 Dirksen Senate Office Building",
+        "rss_url": "http://www.barrasso.senate.gov/public/index.cfm?FuseAction=Rss.Feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "B001261",
+        "birthday": "1952-07-21",
+        "cspanid": 1024777,
+        "firstname": "John",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Barrasso",
+        "link": "https://www.govtrack.us/congress/members/john_barrasso/412251",
+        "middlename": "A.",
+        "name": "Sen. John Barrasso [R-WY]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00006236",
+        "pvsid": "52662",
+        "sortname": "Barrasso, John (Sen.) [R-WY]",
+        "twitterid": "SenJohnBarrasso",
+        "youtubeid": "barrassowyo"
+    },
+    "phone": "202-224-6441",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "WY",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.barrasso.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for New Mexico",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "303 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.heinrich.senate.gov/contact",
+        "fax": "202-225-4975",
+        "office": "303 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "H001046",
+        "birthday": "1971-10-17",
+        "cspanid": 1030686,
+        "firstname": "Martin",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Heinrich",
+        "link": "https://www.govtrack.us/congress/members/martin_heinrich/412281",
+        "middlename": "",
+        "name": "Sen. Martin Heinrich [D-NM]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00029835",
+        "pvsid": "74517",
+        "sortname": "Heinrich, Martin (Sen.) [D-NM]",
+        "twitterid": "MartinHeinrich",
+        "youtubeid": "SenMartinHeinrich"
+    },
+    "phone": "202-224-5521",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "NM",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.heinrich.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for West Virginia",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "306 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.manchin.senate.gov/public/index.cfm/contact-form",
+        "fax": "202-228-0002",
+        "office": "306 Hart Senate Office Building",
+        "rss_url": "http://www.manchin.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "M001183",
+        "birthday": "1947-08-24",
+        "cspanid": 62864,
+        "firstname": "Joe",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Manchin",
+        "link": "https://www.govtrack.us/congress/members/joe_manchin/412391",
+        "middlename": "",
+        "name": "Sen. Joe Manchin [D-WV]",
+        "namemod": "III",
+        "nickname": "",
+        "osid": "N00032838",
+        "pvsid": "7547",
+        "sortname": "Manchin, Joe (Sen.) [D-WV]",
+        "twitterid": "Sen_JoeManchin",
+        "youtubeid": "SenatorJoeManchin"
+    },
+    "phone": "202-224-3954",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "WV",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.manchin.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Massachusetts",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "317 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.warren.senate.gov/?p=email_senator",
+        "fax": "202-228-2072",
+        "office": "317 Hart Senate Office Building",
+        "rss_url": "http://www.warren.senate.gov/rss/?p=hot_topic"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "W000817",
+        "birthday": "1949-06-22",
+        "cspanid": 1023023,
+        "firstname": "Elizabeth",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Warren",
+        "link": "https://www.govtrack.us/congress/members/elizabeth_warren/412542",
+        "middlename": "",
+        "name": "Sen. Elizabeth Warren [D-MA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00033492",
+        "pvsid": "141272",
+        "sortname": "Warren, Elizabeth (Sen.) [D-MA]",
+        "twitterid": "SenWarren",
+        "youtubeid": "senelizabethwarren"
+    },
+    "phone": "202-224-4543",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "MA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.warren.senate.gov"
+},
+{
+    "caucus": "Democrat",
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Maine",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "133 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.king.senate.gov/contact",
+        "fax": "202-224-1946",
+        "office": "133 Hart Senate Office Building",
+        "rss_url": "http://www.king.senate.gov/rss/feeds/?type=all"
+    },
+    "leadership_title": null,
+    "party": "Independent",
+    "person": {
+        "bioguideid": "K000383",
+        "birthday": "1944-03-31",
+        "cspanid": 37413,
+        "firstname": "Angus",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "King",
+        "link": "https://www.govtrack.us/congress/members/angus_king/412545",
+        "middlename": "",
+        "name": "Sen. Angus King [I-ME]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00034580",
+        "pvsid": "22381",
+        "sortname": "King, Angus (Sen.) [I-ME]",
+        "twitterid": "SenAngusKing",
+        "youtubeid": "SenatorAngusKing"
+    },
+    "phone": "202-224-5344",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "ME",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.king.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for North Dakota",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "516 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.heitkamp.senate.gov/public/index.cfm/contact",
+        "fax": "202-224-7776",
+        "office": "516 Hart Senate Office Building",
+        "rss_url": "http://www.heitkamp.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "H001069",
+        "birthday": "1955-10-30",
+        "cspanid": 95414,
+        "firstname": "Heidi",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Heitkamp",
+        "link": "https://www.govtrack.us/congress/members/heidi_heitkamp/412554",
+        "middlename": "",
+        "name": "Sen. Heidi Heitkamp [D-ND]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00033782",
+        "pvsid": "41716",
+        "sortname": "Heitkamp, Heidi (Sen.) [D-ND]",
+        "twitterid": "SenatorHeitkamp",
+        "youtubeid": "senatorheidiheitkamp"
+    },
+    "phone": "202-224-2043",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "ND",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.heitkamp.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Nebraska",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "454 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.fischer.senate.gov/public/index.cfm/contact",
+        "fax": "202-228-1325",
+        "office": "454 Russell Senate Office Building",
+        "rss_url": "http://www.fischer.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "F000463",
+        "birthday": "1951-03-01",
+        "cspanid": 1034067,
+        "firstname": "Deb",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Fischer",
+        "link": "https://www.govtrack.us/congress/members/deb_fischer/412556",
+        "middlename": "",
+        "name": "Sen. Deb Fischer [R-NE]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00033443",
+        "pvsid": "41963",
+        "sortname": "Fischer, Deb (Sen.) [R-NE]",
+        "twitterid": "SenatorFischer",
+        "youtubeid": "senatordebfischer"
+    },
+    "phone": "202-224-6551",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "NE",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.fischer.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Texas",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "404 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.cruz.senate.gov/?p=form&id=16",
+        "fax": "202-228-3398",
+        "office": "404 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "C001098",
+        "birthday": "1970-12-22",
+        "cspanid": 1019953,
+        "firstname": "Ted",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Cruz",
+        "link": "https://www.govtrack.us/congress/members/ted_cruz/412573",
+        "middlename": "",
+        "name": "Sen. Ted Cruz [R-TX]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00033085",
+        "pvsid": "135705",
+        "sortname": "Cruz, Ted (Sen.) [R-TX]",
+        "twitterid": "SenTedCruz",
+        "youtubeid": "sentedcruz"
+    },
+    "phone": "202-224-5922",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "TX",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.cruz.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Virginia",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "231 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.kaine.senate.gov/contact",
+        "fax": "202-228-6363",
+        "office": "231 Russell Senate Office Building",
+        "rss_url": "http://www.kaine.senate.gov/rss/feeds/?type=all"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "K000384",
+        "birthday": "1958-02-26",
+        "cspanid": 49219,
+        "firstname": "Timothy",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Kaine",
+        "link": "https://www.govtrack.us/congress/members/timothy_kaine/412582",
+        "middlename": "",
+        "name": "Sen. Timothy Kaine [D-VA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00033177",
+        "pvsid": "50772",
+        "sortname": "Kaine, Timothy (Sen.) [D-VA]",
+        "twitterid": null,
+        "youtubeid": "SenatorTimKaine"
+    },
+    "phone": "202-224-4024",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2013-01-03",
+    "state": "VA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.kaine.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        113,
+        114,
+        115
+    ],
+    "current": true,
+    "description": "Senior Senator for Mississippi",
+    "district": null,
+    "enddate": "2019-01-03",
+    "extra": {
+        "address": "555 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.wicker.senate.gov/public/index.cfm/contact",
+        "fax": "202-228-0378",
+        "office": "555 Dirksen Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "W000437",
+        "birthday": "1951-07-05",
+        "cspanid": 18203,
+        "firstname": "Roger",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Wicker",
+        "link": "https://www.govtrack.us/congress/members/roger_wicker/400432",
+        "middlename": "F.",
+        "name": "Sen. Roger Wicker [R-MS]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00003280",
+        "pvsid": "21926",
+        "sortname": "Wicker, Roger (Sen.) [R-MS]",
+        "twitterid": "SenatorWicker",
+        "youtubeid": "SenatorWicker"
+    },
+    "phone": "202-224-6253",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class1",
+    "senator_class_label": "Class 1",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2013-01-03",
+    "state": "MS",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.wicker.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Tennessee",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "455 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.alexander.senate.gov/public/index.cfm?p=Email",
+        "fax": "202-228-3398",
+        "office": "455 Dirksen Senate Office Building",
+        "rss_url": "http://www.alexander.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "A000360",
+        "birthday": "1940-07-03",
+        "cspanid": 5,
+        "firstname": "Lamar",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Alexander",
+        "link": "https://www.govtrack.us/congress/members/lamar_alexander/300002",
+        "middlename": "",
+        "name": "Sen. Lamar Alexander [R-TN]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00009888",
+        "pvsid": "15691",
+        "sortname": "Alexander, Lamar (Sen.) [R-TN]",
+        "twitterid": "SenAlexander",
+        "youtubeid": "lamaralexander"
+    },
+    "phone": "202-224-4944",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "TN",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.alexander.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Maine",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "413 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.collins.senate.gov/contact",
+        "fax": "202-224-2693",
+        "office": "413 Dirksen Senate Office Building",
+        "rss_url": "http://www.collins.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "C001035",
+        "birthday": "1952-12-07",
+        "cspanid": 45738,
+        "firstname": "Susan",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Collins",
+        "link": "https://www.govtrack.us/congress/members/susan_collins/300025",
+        "middlename": "M.",
+        "name": "Sen. Susan Collins [R-ME]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00000491",
+        "pvsid": "379",
+        "sortname": "Collins, Susan (Sen.) [R-ME]",
+        "twitterid": "SenatorCollins",
+        "youtubeid": "SenatorSusanCollins"
+    },
+    "phone": "202-224-2523",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "ME",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.collins.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Texas",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "517 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.cornyn.senate.gov/contact",
+        "fax": "202-228-2856",
+        "office": "517 Hart Senate Office Building",
+        "rss_url": "http://www.cornyn.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": "Majority Whip",
+    "party": "Republican",
+    "person": {
+        "bioguideid": "C001056",
+        "birthday": "1952-02-02",
+        "cspanid": 93131,
+        "firstname": "John",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Cornyn",
+        "link": "https://www.govtrack.us/congress/members/john_cornyn/300027",
+        "middlename": "",
+        "name": "Sen. John Cornyn [R-TX]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00024852",
+        "pvsid": "15375",
+        "sortname": "Cornyn, John (Sen.) [R-TX]",
+        "twitterid": "JohnCornyn",
+        "youtubeid": "senjohncornyn"
+    },
+    "phone": "202-224-2934",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "TX",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.cornyn.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Illinois",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "711 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.durbin.senate.gov/contact/",
+        "fax": "202-228-0400",
+        "office": "711 Hart Senate Office Building",
+        "rss_url": "http://durbin.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": "Minority Whip",
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "D000563",
+        "birthday": "1944-11-21",
+        "cspanid": 6741,
+        "firstname": "Richard",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Durbin",
+        "link": "https://www.govtrack.us/congress/members/richard_durbin/300038",
+        "middlename": "J.",
+        "name": "Sen. Richard Durbin [D-IL]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00004981",
+        "pvsid": "26847",
+        "sortname": "Durbin, Richard (Sen.) [D-IL]",
+        "twitterid": "SenatorDurbin",
+        "youtubeid": "SenatorDurbin"
+    },
+    "phone": "202-224-2152",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "IL",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.durbin.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Wyoming",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "379A Russell Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.enzi.senate.gov/public/index.cfm/contact?p=e-mail-senator-enzi",
+        "fax": "202-228-0359",
+        "office": "379a Russell Senate Office Building",
+        "rss_url": "http://www.enzi.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "E000285",
+        "birthday": "1944-02-01",
+        "cspanid": 45824,
+        "firstname": "Michael",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Enzi",
+        "link": "https://www.govtrack.us/congress/members/michael_enzi/300041",
+        "middlename": "B.",
+        "name": "Sen. Michael Enzi [R-WY]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00006249",
+        "pvsid": "558",
+        "sortname": "Enzi, Michael (Sen.) [R-WY]",
+        "twitterid": "SenatorEnzi",
+        "youtubeid": "senatorenzi"
+    },
+    "phone": "202-224-3424",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "WY",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.enzi.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for South Carolina",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "290 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.lgraham.senate.gov/public/index.cfm/e-mail-senator-graham",
+        "fax": "202-224-3808",
+        "office": "290 Russell Senate Office Building",
+        "rss_url": "http://www.lgraham.senate.gov/public/index.cfm?FuseAction=Rss.Feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "G000359",
+        "birthday": "1955-07-09",
+        "cspanid": 36782,
+        "firstname": "Lindsey",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Graham",
+        "link": "https://www.govtrack.us/congress/members/lindsey_graham/300047",
+        "middlename": "O.",
+        "name": "Sen. Lindsey Graham [R-SC]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00009975",
+        "pvsid": "21992",
+        "sortname": "Graham, Lindsey (Sen.) [R-SC]",
+        "twitterid": "GrahamBlog",
+        "youtubeid": "USSenLindseyGraham"
+    },
+    "phone": "202-224-5972",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "SC",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.lgraham.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Oklahoma",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "205 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.inhofe.senate.gov/contact",
+        "fax": "202-228-0380",
+        "office": "205 Russell Senate Office Building",
+        "rss_url": "http://www.inhofe.senate.gov/rss/feeds/?type=all&amp;cachebuster=eea6c4d7%2d939c%2d5c1e%2db6c7aa3b8b291208"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "I000024",
+        "birthday": "1934-11-17",
+        "cspanid": 5619,
+        "firstname": "James",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Inhofe",
+        "link": "https://www.govtrack.us/congress/members/james_inhofe/300055",
+        "middlename": "M.",
+        "name": "Sen. James “Jim” Inhofe [R-OK]",
+        "namemod": "",
+        "nickname": "Jim",
+        "osid": "N00005582",
+        "pvsid": "27027",
+        "sortname": "Inhofe, James “Jim” (Sen.) [R-OK]",
+        "twitterid": "InhofePress",
+        "youtubeid": "jiminhofepressoffice"
+    },
+    "phone": "202-224-4721",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "OK",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.inhofe.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Kentucky",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "317 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.mcconnell.senate.gov/public/index.cfm?p=contact",
+        "fax": "202-224-2499",
+        "office": "317 Russell Senate Office Building",
+        "rss_url": "http://www.mcconnell.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": "Majority Leader",
+    "party": "Republican",
+    "person": {
+        "bioguideid": "M000355",
+        "birthday": "1942-02-20",
+        "cspanid": 2351,
+        "firstname": "Mitch",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "McConnell",
+        "link": "https://www.govtrack.us/congress/members/mitch_mcconnell/300072",
+        "middlename": "",
+        "name": "Sen. Mitch McConnell [R-KY]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00003389",
+        "pvsid": "53298",
+        "sortname": "McConnell, Mitch (Sen.) [R-KY]",
+        "twitterid": "McConnellPress",
+        "youtubeid": null
+    },
+    "phone": "202-224-2541",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "KY",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.mcconnell.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Rhode Island",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "728 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.reed.senate.gov/contact/",
+        "fax": "202-224-4680",
+        "office": "728 Hart Senate Office Building",
+        "rss_url": "https://www.reed.senate.gov//rss/feeds/?type=all"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "R000122",
+        "birthday": "1949-11-12",
+        "cspanid": 24239,
+        "firstname": "John",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Reed",
+        "link": "https://www.govtrack.us/congress/members/john_reed/300081",
+        "middlename": "F.",
+        "name": "Sen. John “Jack” Reed [D-RI]",
+        "namemod": "",
+        "nickname": "Jack",
+        "osid": "N00000362",
+        "pvsid": "27060",
+        "sortname": "Reed, John “Jack” (Sen.) [D-RI]",
+        "twitterid": "SenJackReed",
+        "youtubeid": "SenatorReed"
+    },
+    "phone": "202-224-4642",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "RI",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.reed.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Kansas",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "109 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.roberts.senate.gov/public/?p=EmailPat",
+        "fax": "202-224-3514",
+        "office": "109 Hart Senate Office Building",
+        "rss_url": "http://www.roberts.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "R000307",
+        "birthday": "1936-04-20",
+        "cspanid": 16354,
+        "firstname": "Pat",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Roberts",
+        "link": "https://www.govtrack.us/congress/members/pat_roberts/300083",
+        "middlename": "",
+        "name": "Sen. Pat Roberts [R-KS]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00005285",
+        "pvsid": "26866",
+        "sortname": "Roberts, Pat (Sen.) [R-KS]",
+        "twitterid": "SenPatRoberts",
+        "youtubeid": "SenPatRoberts"
+    },
+    "phone": "202-224-4774",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "KS",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.roberts.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for West Virginia",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "172 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.capito.senate.gov/contact/contact-shelley",
+        "fax": "202-225-7856",
+        "office": "172 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "C001047",
+        "birthday": "1953-11-26",
+        "cspanid": 83737,
+        "firstname": "Shelley",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Capito",
+        "link": "https://www.govtrack.us/congress/members/shelley_capito/400061",
+        "middlename": "Moore",
+        "name": "Sen. Shelley Capito [R-WV]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00009771",
+        "pvsid": "11701",
+        "sortname": "Capito, Shelley (Sen.) [R-WV]",
+        "twitterid": "SenCapito",
+        "youtubeid": null
+    },
+    "phone": "202-224-6472",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "WV",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.capito.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Massachusetts",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "255 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.markey.senate.gov/contact",
+        "office": "255 Dirksen Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "M000133",
+        "birthday": "1946-07-11",
+        "cspanid": 260,
+        "firstname": "Edward",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Markey",
+        "link": "https://www.govtrack.us/congress/members/edward_markey/400253",
+        "middlename": "J.",
+        "name": "Sen. Edward “Ed” Markey [D-MA]",
+        "namemod": "",
+        "nickname": "Ed",
+        "osid": "N00000270",
+        "pvsid": "26900",
+        "sortname": "Markey, Edward “Ed” (Sen.) [D-MA]",
+        "twitterid": "SenMarkey",
+        "youtubeid": "RepMarkey"
+    },
+    "phone": "202-224-2742",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "MA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.markey.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for New Mexico",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "531 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.tomudall.senate.gov/?p=contact",
+        "fax": "202-228-3261",
+        "office": "531 Hart Senate Office Building",
+        "rss_url": "http://tomudall.senate.gov/rss/?p=blog"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "U000039",
+        "birthday": "1948-05-18",
+        "cspanid": 10075,
+        "firstname": "Tom",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Udall",
+        "link": "https://www.govtrack.us/congress/members/tom_udall/400413",
+        "middlename": "S.",
+        "name": "Sen. Tom Udall [D-NM]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00006561",
+        "pvsid": "22658",
+        "sortname": "Udall, Tom (Sen.) [D-NM]",
+        "twitterid": "SenatorTomUdall",
+        "youtubeid": "senatortomudall"
+    },
+    "phone": "202-224-6621",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "NM",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.tomudall.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Louisiana",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "520 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.cassidy.senate.gov/contact",
+        "fax": "202-225-7313",
+        "office": "520 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "C001075",
+        "birthday": "1957-09-28",
+        "cspanid": 1030546,
+        "firstname": "Bill",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Cassidy",
+        "link": "https://www.govtrack.us/congress/members/bill_cassidy/412269",
+        "middlename": "",
+        "name": "Sen. Bill Cassidy [R-LA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00030245",
+        "pvsid": "69494",
+        "sortname": "Cassidy, Bill (Sen.) [R-LA]",
+        "twitterid": null,
+        "youtubeid": "SenatorBillCassidy"
+    },
+    "phone": "202-224-5824",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "LA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.cassidy.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Michigan",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "724 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.peters.senate.gov/contact/email-gary",
+        "fax": "202-226-2356",
+        "office": "724 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "P000595",
+        "birthday": "1958-12-01",
+        "cspanid": 50199,
+        "firstname": "Gary",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Peters",
+        "link": "https://www.govtrack.us/congress/members/gary_peters/412305",
+        "middlename": "C.",
+        "name": "Sen. Gary Peters [D-MI]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00029277",
+        "pvsid": "8749",
+        "sortname": "Peters, Gary (Sen.) [D-MI]",
+        "twitterid": "SenGaryPeters",
+        "youtubeid": "RepGaryPeters"
+    },
+    "phone": "202-224-6221",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "MI",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.peters.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for Virginia",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "703 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.warner.senate.gov/public/index.cfm?p=Contact",
+        "fax": "202-224-6295",
+        "office": "703 Hart Senate Office Building",
+        "rss_url": "http://www.warner.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "W000805",
+        "birthday": "1954-12-15",
+        "cspanid": 7630,
+        "firstname": "Mark",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Warner",
+        "link": "https://www.govtrack.us/congress/members/mark_warner/412321",
+        "middlename": "",
+        "name": "Sen. Mark Warner [D-VA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00002097",
+        "pvsid": "535",
+        "sortname": "Warner, Mark (Sen.) [D-VA]",
+        "twitterid": "MarkWarner",
+        "youtubeid": "SenatorMarkWarner"
+    },
+    "phone": "202-224-2023",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "VA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.warner.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Idaho",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "483 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.risch.senate.gov/public/index.cfm?p=Email",
+        "fax": "202-224-2573",
+        "office": "483 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "R000584",
+        "birthday": "1943-05-03",
+        "cspanid": 1020034,
+        "firstname": "James",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Risch",
+        "link": "https://www.govtrack.us/congress/members/james_risch/412322",
+        "middlename": "",
+        "name": "Sen. James Risch [R-ID]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00029441",
+        "pvsid": "2919",
+        "sortname": "Risch, James (Sen.) [R-ID]",
+        "twitterid": "SenatorRisch",
+        "youtubeid": "SenatorJamesRisch"
+    },
+    "phone": "202-224-2752",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "ID",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.risch.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Senior Senator for New Hampshire",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "506 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.shaheen.senate.gov/contact/contact-jeanne",
+        "fax": "202-228-3194",
+        "office": "506 Hart Senate Office Building",
+        "rss_url": "http://www.shaheen.senate.gov/rss/"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "S001181",
+        "birthday": "1947-01-28",
+        "cspanid": 22850,
+        "firstname": "Jeanne",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Shaheen",
+        "link": "https://www.govtrack.us/congress/members/jeanne_shaheen/412323",
+        "middlename": "",
+        "name": "Sen. Jeanne Shaheen [D-NH]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00024790",
+        "pvsid": "1663",
+        "sortname": "Shaheen, Jeanne (Sen.) [D-NH]",
+        "twitterid": "SenatorShaheen",
+        "youtubeid": "senatorshaheen"
+    },
+    "phone": "202-224-2841",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2015-01-06",
+    "state": "NH",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.shaheen.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Oregon",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "313 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.merkley.senate.gov/contact/",
+        "fax": "202-228-3997",
+        "office": "313 Hart Senate Office Building",
+        "rss_url": "http://www.merkley.senate.gov/rss/"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "M001176",
+        "birthday": "1956-10-24",
+        "cspanid": 1029842,
+        "firstname": "Jeff",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Merkley",
+        "link": "https://www.govtrack.us/congress/members/jeff_merkley/412325",
+        "middlename": "",
+        "name": "Sen. Jeff Merkley [D-OR]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00029303",
+        "pvsid": "23644",
+        "sortname": "Merkley, Jeff (Sen.) [D-OR]",
+        "twitterid": "SenJeffMerkley",
+        "youtubeid": "SenatorJeffMerkley"
+    },
+    "phone": "202-224-3753",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "OR",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.merkley.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Delaware",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "127A Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.coons.senate.gov/contact",
+        "fax": "202-228-3075",
+        "office": "127a Russell Senate Office Building",
+        "rss_url": "http://www.coons.senate.gov/rss/feeds/?type=all"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "C001088",
+        "birthday": "1963-09-09",
+        "cspanid": 9269028,
+        "firstname": "Chris",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Coons",
+        "link": "https://www.govtrack.us/congress/members/chris_coons/412390",
+        "middlename": "Andrew",
+        "name": "Sen. Chris Coons [D-DE]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00031820",
+        "pvsid": "122834",
+        "sortname": "Coons, Chris (Sen.) [D-DE]",
+        "twitterid": "ChrisCoons",
+        "youtubeid": "senatorchriscoons"
+    },
+    "phone": "202-224-5042",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "DE",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.coons.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Colorado",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "354 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.gardner.senate.gov/contact-cory/email-cory",
+        "fax": "202-225-5870",
+        "office": "354 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "G000562",
+        "birthday": "1974-08-22",
+        "cspanid": 623308,
+        "firstname": "Cory",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Gardner",
+        "link": "https://www.govtrack.us/congress/members/cory_gardner/412406",
+        "middlename": "",
+        "name": "Sen. Cory Gardner [R-CO]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00030780",
+        "pvsid": "30004",
+        "sortname": "Gardner, Cory (Sen.) [R-CO]",
+        "twitterid": "SenCoryGardner",
+        "youtubeid": null
+    },
+    "phone": "202-224-5941",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "CO",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.gardner.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Arkansas",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "124 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.cotton.senate.gov/?p=contact",
+        "office": "124 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "C001095",
+        "birthday": "1977-05-13",
+        "cspanid": 63928,
+        "firstname": "Tom",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Cotton",
+        "link": "https://www.govtrack.us/congress/members/tom_cotton/412508",
+        "middlename": "",
+        "name": "Sen. Tom Cotton [R-AR]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00033363",
+        "pvsid": "135651",
+        "sortname": "Cotton, Tom (Sen.) [R-AR]",
+        "twitterid": "SenTomCotton",
+        "youtubeid": "RepTomCotton"
+    },
+    "phone": "202-224-2353",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "AR",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.cotton.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Montana",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "320 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.daines.senate.gov/connect/email-steve",
+        "fax": "202-228-1236",
+        "office": "320 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "D000618",
+        "birthday": "1962-08-20",
+        "cspanid": 1034037,
+        "firstname": "Steve",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Daines",
+        "link": "https://www.govtrack.us/congress/members/steve_daines/412549",
+        "middlename": "",
+        "name": "Sen. Steve Daines [R-MT]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00033054",
+        "pvsid": "135720",
+        "sortname": "Daines, Steve (Sen.) [R-MT]",
+        "twitterid": "SteveDaines",
+        "youtubeid": "SteveDainesMT"
+    },
+    "phone": "202-224-2651",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "MT",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.daines.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for New Jersey",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "359 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.booker.senate.gov/?p=contact",
+        "fax": "202-224-8378",
+        "office": "359 Dirksen Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "B001288",
+        "birthday": "1969-04-27",
+        "cspanid": 84679,
+        "firstname": "Cory",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Booker",
+        "link": "https://www.govtrack.us/congress/members/cory_booker/412598",
+        "middlename": "Anthony",
+        "name": "Sen. Cory Booker [D-NJ]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00035267",
+        "pvsid": "76151",
+        "sortname": "Booker, Cory (Sen.) [D-NJ]",
+        "twitterid": "SenBooker",
+        "youtubeid": "SenCoryBooker"
+    },
+    "phone": "202-224-3224",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "NJ",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.booker.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Alaska",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "702 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.sullivan.senate.gov/contact/email",
+        "office": "702 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "S001198",
+        "birthday": "1964-11-13",
+        "cspanid": 1023262,
+        "firstname": "Dan",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Sullivan",
+        "link": "https://www.govtrack.us/congress/members/dan_sullivan/412665",
+        "middlename": "",
+        "name": "Sen. Dan Sullivan [R-AK]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00035774",
+        "pvsid": "114964",
+        "sortname": "Sullivan, Dan (Sen.) [R-AK]",
+        "twitterid": "SenDanSullivan",
+        "youtubeid": null
+    },
+    "phone": "202-224-3004",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "AK",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.sullivan.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Georgia",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "455 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.perdue.senate.gov/connect/email",
+        "office": "455 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "P000612",
+        "birthday": "1949-12-10",
+        "cspanid": 75920,
+        "firstname": "David",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Perdue",
+        "link": "https://www.govtrack.us/congress/members/david_perdue/412666",
+        "middlename": "",
+        "name": "Sen. David Perdue [R-GA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00035516",
+        "pvsid": "151330",
+        "sortname": "Perdue, David (Sen.) [R-GA]",
+        "twitterid": "sendavidperdue",
+        "youtubeid": null
+    },
+    "phone": "202-224-3521",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "GA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.perdue.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Iowa",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "111 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.ernst.senate.gov/public/index.cfm/contact",
+        "office": "111 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "E000295",
+        "birthday": "1970-07-01",
+        "cspanid": 75342,
+        "firstname": "Joni",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Ernst",
+        "link": "https://www.govtrack.us/congress/members/joni_ernst/412667",
+        "middlename": "",
+        "name": "Sen. Joni Ernst [R-IA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00035483",
+        "pvsid": "128583",
+        "sortname": "Ernst, Joni (Sen.) [R-IA]",
+        "twitterid": "SenJoniErnst",
+        "youtubeid": null
+    },
+    "phone": "202-224-3254",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "IA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.ernst.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for North Carolina",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "185 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.tillis.senate.gov/public/index.cfm/email-me",
+        "office": "185 Dirksen Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "T000476",
+        "birthday": "1960-08-30",
+        "cspanid": 77055,
+        "firstname": "Thom",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Tillis",
+        "link": "https://www.govtrack.us/congress/members/thom_tillis/412668",
+        "middlename": "",
+        "name": "Sen. Thom Tillis [R-NC]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00035492",
+        "pvsid": "57717",
+        "sortname": "Tillis, Thom (Sen.) [R-NC]",
+        "twitterid": "senthomtillis",
+        "youtubeid": null
+    },
+    "phone": "202-224-6342",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "NC",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.tillis.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for South Dakota",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "502 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.rounds.senate.gov/contact/email-mike",
+        "office": "502 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "R000605",
+        "birthday": "1954-10-24",
+        "cspanid": 78317,
+        "firstname": "Mike",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Rounds",
+        "link": "https://www.govtrack.us/congress/members/mike_rounds/412669",
+        "middlename": "",
+        "name": "Sen. Mike Rounds [R-SD]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00035187",
+        "pvsid": "7455",
+        "sortname": "Rounds, Mike (Sen.) [R-SD]",
+        "twitterid": "SenatorRounds",
+        "youtubeid": null
+    },
+    "phone": "202-224-5842",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "SD",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.rounds.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        114,
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Nebraska",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "136 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.sasse.senate.gov/public/index.cfm/email-ben",
+        "office": "136 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "S001197",
+        "birthday": "1972-02-22",
+        "cspanid": 77429,
+        "firstname": "Benjamin",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Sasse",
+        "link": "https://www.govtrack.us/congress/members/benjamin_sasse/412671",
+        "middlename": "Eric",
+        "name": "Sen. Benjamin Sasse [R-NE]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00035544",
+        "pvsid": "150182",
+        "sortname": "Sasse, Benjamin (Sen.) [R-NE]",
+        "twitterid": "SenSasse",
+        "youtubeid": null
+    },
+    "phone": "202-224-4224",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2015-01-06",
+    "state": "NE",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.sasse.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Idaho",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "239 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.crapo.senate.gov/contact",
+        "fax": "202-228-1375",
+        "office": "239 Dirksen Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "C000880",
+        "birthday": "1951-05-20",
+        "cspanid": 26440,
+        "firstname": "Michael",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Crapo",
+        "link": "https://www.govtrack.us/congress/members/michael_crapo/300030",
+        "middlename": "D.",
+        "name": "Sen. Michael Crapo [R-ID]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00006267",
+        "pvsid": "26830",
+        "sortname": "Crapo, Michael (Sen.) [R-ID]",
+        "twitterid": "MikeCrapo",
+        "youtubeid": "senatorcrapo"
+    },
+    "phone": "202-224-6142",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "ID",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.crapo.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Iowa",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "135 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.grassley.senate.gov/contact",
+        "fax": "202-224-6020",
+        "office": "135 Hart Senate Office Building",
+        "rss_url": "http://grassley.senate.gov/customcf/rss_feed.cfm"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "G000386",
+        "birthday": "1933-09-17",
+        "cspanid": 1167,
+        "firstname": "Charles",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Grassley",
+        "link": "https://www.govtrack.us/congress/members/charles_grassley/300048",
+        "middlename": "E.",
+        "name": "Sen. Charles “Chuck” Grassley [R-IA]",
+        "namemod": "",
+        "nickname": "Chuck",
+        "osid": "N00001758",
+        "pvsid": "53293",
+        "sortname": "Grassley, Charles “Chuck” (Sen.) [R-IA]",
+        "twitterid": "ChuckGrassley",
+        "youtubeid": "senchuckgrassley"
+    },
+    "phone": "202-224-3744",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "IA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.grassley.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Vermont",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "437 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.leahy.senate.gov/contact/",
+        "fax": "202-224-3479",
+        "office": "437 Russell Senate Office Building",
+        "rss_url": "http://www.leahy.senate.gov/rss/feeds/press/"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "L000174",
+        "birthday": "1940-03-31",
+        "cspanid": 1552,
+        "firstname": "Patrick",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Leahy",
+        "link": "https://www.govtrack.us/congress/members/patrick_leahy/300065",
+        "middlename": "J.",
+        "name": "Sen. Patrick Leahy [D-VT]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00009918",
+        "pvsid": "53353",
+        "sortname": "Leahy, Patrick (Sen.) [D-VT]",
+        "twitterid": "SenatorLeahy",
+        "youtubeid": "SenatorPatrickLeahy"
+    },
+    "phone": "202-224-4242",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "VT",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.leahy.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Alaska",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "522 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.murkowski.senate.gov/public/index.cfm/contact",
+        "fax": "202-224-5301",
+        "office": "522 Hart Senate Office Building",
+        "rss_url": "http://www.murkowski.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "M001153",
+        "birthday": "1957-05-22",
+        "cspanid": 1004138,
+        "firstname": "Lisa",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Murkowski",
+        "link": "https://www.govtrack.us/congress/members/lisa_murkowski/300075",
+        "middlename": "A.",
+        "name": "Sen. Lisa Murkowski [R-AK]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00026050",
+        "pvsid": "15841",
+        "sortname": "Murkowski, Lisa (Sen.) [R-AK]",
+        "twitterid": "LisaMurkowski",
+        "youtubeid": "senatormurkowski"
+    },
+    "phone": "202-224-6665",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "AK",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.murkowski.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Washington",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "154 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.murray.senate.gov/public/index.cfm/contactme",
+        "fax": "202-224-0238",
+        "office": "154 Russell Senate Office Building",
+        "rss_url": "http://www.murray.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "M001111",
+        "birthday": "1950-10-11",
+        "cspanid": 25277,
+        "firstname": "Patty",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Murray",
+        "link": "https://www.govtrack.us/congress/members/patty_murray/300076",
+        "middlename": "",
+        "name": "Sen. Patty Murray [D-WA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00007876",
+        "pvsid": "53358",
+        "sortname": "Murray, Patty (Sen.) [D-WA]",
+        "twitterid": "PattyMurray",
+        "youtubeid": "SenatorPattyMurray"
+    },
+    "phone": "202-224-2621",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "WA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.murray.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for New York",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "322 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.schumer.senate.gov/contact/email-chuck",
+        "fax": "202-228-3027",
+        "office": "322 Hart Senate Office Building"
+    },
+    "leadership_title": "Minority Leader",
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "S000148",
+        "birthday": "1950-11-23",
+        "cspanid": 5929,
+        "firstname": "Charles",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Schumer",
+        "link": "https://www.govtrack.us/congress/members/charles_schumer/300087",
+        "middlename": "E.",
+        "name": "Sen. Charles “Chuck” Schumer [D-NY]",
+        "namemod": "",
+        "nickname": "Chuck",
+        "osid": "N00001093",
+        "pvsid": "26976",
+        "sortname": "Schumer, Charles “Chuck” (Sen.) [D-NY]",
+        "twitterid": "SenSchumer",
+        "youtubeid": "SenatorSchumer"
+    },
+    "phone": "202-224-6542",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "NY",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.schumer.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Alabama",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "304 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.shelby.senate.gov/public/index.cfm/emailsenatorshelby",
+        "fax": "202-224-3416",
+        "office": "304 Russell Senate Office Building",
+        "rss_url": "http://www.shelby.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "S000320",
+        "birthday": "1934-05-06",
+        "cspanid": 1859,
+        "firstname": "Richard",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Shelby",
+        "link": "https://www.govtrack.us/congress/members/richard_shelby/300089",
+        "middlename": "C.",
+        "name": "Sen. Richard Shelby [R-AL]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00009920",
+        "pvsid": "53266",
+        "sortname": "Shelby, Richard (Sen.) [R-AL]",
+        "twitterid": "SenShelby",
+        "youtubeid": "SenatorRichardShelby"
+    },
+    "phone": "202-224-5744",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "AL",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.shelby.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Oregon",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "221 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.wyden.senate.gov/contact/",
+        "fax": "202-228-2717",
+        "office": "221 Dirksen Senate Office Building",
+        "rss_url": "http://www.wyden.senate.gov/rss/feeds/?type=all&amp;"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "W000779",
+        "birthday": "1949-05-03",
+        "cspanid": 1962,
+        "firstname": "Ron",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Wyden",
+        "link": "https://www.govtrack.us/congress/members/ron_wyden/300100",
+        "middlename": "",
+        "name": "Sen. Ron Wyden [D-OR]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00007724",
+        "pvsid": "27036",
+        "sortname": "Wyden, Ron (Sen.) [D-OR]",
+        "twitterid": "RonWyden",
+        "youtubeid": "senronwyden"
+    },
+    "phone": "202-224-5244",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "OR",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.wyden.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Missouri",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "260 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.blunt.senate.gov/public/index.cfm/contact-roy",
+        "fax": "202-224-8149",
+        "office": "260 Russell Senate Office Building",
+        "rss_url": "http://www.blunt.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "B000575",
+        "birthday": "1950-01-10",
+        "cspanid": 45465,
+        "firstname": "Roy",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Blunt",
+        "link": "https://www.govtrack.us/congress/members/roy_blunt/400034",
+        "middlename": "",
+        "name": "Sen. Roy Blunt [R-MO]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00005195",
+        "pvsid": "418",
+        "sortname": "Blunt, Roy (Sen.) [R-MO]",
+        "twitterid": "RoyBlunt",
+        "youtubeid": "SenatorBlunt"
+    },
+    "phone": "202-224-5721",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "MO",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.blunt.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Arkansas",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "141 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.boozman.senate.gov/public/index.cfm/contact",
+        "fax": "202-228-1371",
+        "office": "141 Hart Senate Office Building",
+        "rss_url": "http://www.boozman.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "B001236",
+        "birthday": "1950-12-10",
+        "cspanid": 92069,
+        "firstname": "John",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Boozman",
+        "link": "https://www.govtrack.us/congress/members/john_boozman/400040",
+        "middlename": "",
+        "name": "Sen. John Boozman [R-AR]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00013873",
+        "pvsid": "27958",
+        "sortname": "Boozman, John (Sen.) [R-AR]",
+        "twitterid": "JohnBoozman",
+        "youtubeid": "BoozmanPressOffice"
+    },
+    "phone": "202-224-4843",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "AR",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.boozman.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for North Carolina",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "217 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.burr.senate.gov/contact/email",
+        "fax": "202-228-2981",
+        "office": "217 Russell Senate Office Building",
+        "rss_url": "http://www.burr.senate.gov/public/index.cfm?fuseaction=rss.feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "B001135",
+        "birthday": "1955-11-30",
+        "cspanid": 31054,
+        "firstname": "Richard",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Burr",
+        "link": "https://www.govtrack.us/congress/members/richard_burr/400054",
+        "middlename": "M.",
+        "name": "Sen. Richard Burr [R-NC]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00002221",
+        "pvsid": "21787",
+        "sortname": "Burr, Richard (Sen.) [R-NC]",
+        "twitterid": "SenatorBurr",
+        "youtubeid": "SenatorRichardBurr"
+    },
+    "phone": "202-224-3154",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "NC",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.burr.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Georgia",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "131 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.isakson.senate.gov/public/index.cfm/email-me",
+        "fax": "202-228-0724",
+        "office": "131 Russell Senate Office Building",
+        "rss_url": "http://www.isakson.senate.gov/public/?a=RSS.Feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "I000055",
+        "birthday": "1944-12-28",
+        "cspanid": 59135,
+        "firstname": "John",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Isakson",
+        "link": "https://www.govtrack.us/congress/members/john_isakson/400194",
+        "middlename": "H.",
+        "name": "Sen. John “Johnny” Isakson [R-GA]",
+        "namemod": "",
+        "nickname": "Johnny",
+        "osid": "N00002593",
+        "pvsid": "1721",
+        "sortname": "Isakson, John “Johnny” (Sen.) [R-GA]",
+        "twitterid": "SenatorIsakson",
+        "youtubeid": "SenatorIsakson"
+    },
+    "phone": "202-224-3643",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "GA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.isakson.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Kansas",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "521 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.moran.senate.gov/public/index.cfm/e-mail-jerry",
+        "fax": "202-228-6966",
+        "office": "521 Dirksen Senate Office Building",
+        "rss_url": "http://www.moran.senate.gov/public/index.cfm/rss/feed/"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "M000934",
+        "birthday": "1954-05-29",
+        "cspanid": 45469,
+        "firstname": "Jerry",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Moran",
+        "link": "https://www.govtrack.us/congress/members/jerry_moran/400284",
+        "middlename": "",
+        "name": "Sen. Jerry Moran [R-KS]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00005282",
+        "pvsid": "542",
+        "sortname": "Moran, Jerry (Sen.) [R-KS]",
+        "twitterid": "JerryMoran",
+        "youtubeid": "senatorjerrymoran"
+    },
+    "phone": "202-224-6521",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "KS",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.moran.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Ohio",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "448 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.portman.senate.gov/public/index.cfm/contact?p=contact-form",
+        "office": "448 Russell Senate Office Building",
+        "rss_url": "http://www.portman.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "P000449",
+        "birthday": "1955-12-19",
+        "cspanid": 31819,
+        "firstname": "Robert",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Portman",
+        "link": "https://www.govtrack.us/congress/members/robert_portman/400325",
+        "middlename": "J.",
+        "name": "Sen. Robert “Rob” Portman [R-OH]",
+        "namemod": "",
+        "nickname": "Rob",
+        "osid": "N00003682",
+        "pvsid": "27008",
+        "sortname": "Portman, Robert “Rob” (Sen.) [R-OH]",
+        "twitterid": "SenRobPortman",
+        "youtubeid": "SenRobPortman"
+    },
+    "phone": "202-224-3353",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "OH",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.portman.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Pennsylvania",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "248 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.toomey.senate.gov/?p=contact",
+        "fax": "202-228-0284",
+        "office": "248 Russell Senate Office Building",
+        "rss_url": "http://toomey.senate.gov/rss/?p=hot_topic"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "T000461",
+        "birthday": "1961-11-17",
+        "cspanid": 7958,
+        "firstname": "Patrick",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Toomey",
+        "link": "https://www.govtrack.us/congress/members/patrick_toomey/400408",
+        "middlename": "J.",
+        "name": "Sen. Patrick “Pat” Toomey [R-PA]",
+        "namemod": "",
+        "nickname": "Pat",
+        "osid": "N00001489",
+        "pvsid": "24096",
+        "sortname": "Toomey, Patrick “Pat” (Sen.) [R-PA]",
+        "twitterid": "SenToomey",
+        "youtubeid": "sentoomey"
+    },
+    "phone": "202-224-4254",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "PA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.toomey.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Maryland",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "110 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.vanhollen.senate.gov/contact/email",
+        "fax": "202-225-0375",
+        "office": "110 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "V000128",
+        "birthday": "1959-01-10",
+        "cspanid": 20756,
+        "firstname": "Chris",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Van Hollen",
+        "link": "https://www.govtrack.us/congress/members/chris_van_hollen/400415",
+        "middlename": "",
+        "name": "Sen. Chris Van Hollen [D-MD]",
+        "namemod": "Jr.",
+        "nickname": "",
+        "osid": "N00013820",
+        "pvsid": "6098",
+        "sortname": "Van Hollen, Chris (Sen.) [D-MD]",
+        "twitterid": "ChrisVanHollen",
+        "youtubeid": "RepChrisVanHollen"
+    },
+    "phone": "202-224-4654",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "MD",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.vanhollen.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for South Dakota",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "511 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.thune.senate.gov/public/index.cfm/contact",
+        "fax": "202-228-5429",
+        "office": "511 Dirksen Senate Office Building",
+        "rss_url": "http://www.thune.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "T000250",
+        "birthday": "1961-01-07",
+        "cspanid": 45552,
+        "firstname": "John",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Thune",
+        "link": "https://www.govtrack.us/congress/members/john_thune/400546",
+        "middlename": "",
+        "name": "Sen. John Thune [R-SD]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00004572",
+        "pvsid": "398",
+        "sortname": "Thune, John (Sen.) [R-SD]",
+        "twitterid": "SenJohnThune",
+        "youtubeid": "johnthune"
+    },
+    "phone": "202-224-2321",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "SD",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.thune.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Colorado",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "261 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.bennet.senate.gov/public/index.cfm/contact",
+        "fax": "202-228-5097",
+        "office": "261 Russell Senate Office Building",
+        "rss_url": "http://www.bennet.senate.gov/rss/feeds/?type=news"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "B001267",
+        "birthday": "1964-11-28",
+        "cspanid": 1031622,
+        "firstname": "Michael",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Bennet",
+        "link": "https://www.govtrack.us/congress/members/michael_bennet/412330",
+        "middlename": "F.",
+        "name": "Sen. Michael Bennet [D-CO]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00030608",
+        "pvsid": "110942",
+        "sortname": "Bennet, Michael (Sen.) [D-CO]",
+        "twitterid": "SenBennetCo",
+        "youtubeid": "SenatorBennet"
+    },
+    "phone": "202-224-5852",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "CO",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.bennet.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Indiana",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "400 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.young.senate.gov/contact",
+        "fax": "202-226-6866",
+        "office": "400 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "Y000064",
+        "birthday": "1972-08-24",
+        "cspanid": 1033743,
+        "firstname": "Todd",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Young",
+        "link": "https://www.govtrack.us/congress/members/todd_young/412428",
+        "middlename": "C.",
+        "name": "Sen. Todd Young [R-IN]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00030670",
+        "pvsid": "120345",
+        "sortname": "Young, Todd (Sen.) [R-IN]",
+        "twitterid": "SenToddYoung",
+        "youtubeid": "RepToddYoung"
+    },
+    "phone": "202-224-5623",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "IN",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.young.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Oklahoma",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "316 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.lankford.senate.gov/contact/email",
+        "office": "316 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "L000575",
+        "birthday": "1968-03-04",
+        "cspanid": 1033847,
+        "firstname": "James",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Lankford",
+        "link": "https://www.govtrack.us/congress/members/james_lankford/412464",
+        "middlename": "",
+        "name": "Sen. James Lankford [R-OK]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00031129",
+        "pvsid": "124938",
+        "sortname": "Lankford, James (Sen.) [R-OK]",
+        "twitterid": "SenatorLankford",
+        "youtubeid": "SenatorLankford"
+    },
+    "phone": "202-224-5754",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "OK",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.lankford.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for South Carolina",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "717 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.scott.senate.gov/contact/email-me",
+        "fax": "202-225-3407",
+        "office": "717 Hart Senate Office Building",
+        "rss_url": "http://www.scott.senate.gov/rss.xml"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "S001184",
+        "birthday": "1965-09-19",
+        "cspanid": 623506,
+        "firstname": "Tim",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Scott",
+        "link": "https://www.govtrack.us/congress/members/tim_scott/412471",
+        "middlename": "",
+        "name": "Sen. Tim Scott [R-SC]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00031782",
+        "pvsid": "11940",
+        "sortname": "Scott, Tim (Sen.) [R-SC]",
+        "twitterid": "SenatorTimScott",
+        "youtubeid": "SenatorTimScott"
+    },
+    "phone": "202-224-6121",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "SC",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.scott.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Connecticut",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "706 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.blumenthal.senate.gov/contact/",
+        "fax": "202-224-9673",
+        "office": "706 Hart Senate Office Building",
+        "rss_url": "http://www.blumenthal.senate.gov/rss/feeds/?type=all"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "B001277",
+        "birthday": "1946-02-13",
+        "cspanid": 21799,
+        "firstname": "Richard",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Blumenthal",
+        "link": "https://www.govtrack.us/congress/members/richard_blumenthal/412490",
+        "middlename": "",
+        "name": "Sen. Richard Blumenthal [D-CT]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00031685",
+        "pvsid": "1568",
+        "sortname": "Blumenthal, Richard (Sen.) [D-CT]",
+        "twitterid": "SenBlumenthal",
+        "youtubeid": "SenatorBlumenthal"
+    },
+    "phone": "202-224-2823",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "CT",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.blumenthal.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Florida",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "284 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.rubio.senate.gov/public/index.cfm/contact",
+        "fax": "202-228-0285",
+        "office": "284 Russell Senate Office Building",
+        "rss_url": "http://www.rubio.senate.gov/public/?a=rss.feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "R000595",
+        "birthday": "1971-05-28",
+        "cspanid": 87599,
+        "firstname": "Marco",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Rubio",
+        "link": "https://www.govtrack.us/congress/members/marco_rubio/412491",
+        "middlename": "",
+        "name": "Sen. Marco Rubio [R-FL]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00030612",
+        "pvsid": "1601",
+        "sortname": "Rubio, Marco (Sen.) [R-FL]",
+        "twitterid": "SenRubioPress",
+        "youtubeid": "SenatorMarcoRubio"
+    },
+    "phone": "202-224-3041",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "FL",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.rubio.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Kentucky",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "167 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.paul.senate.gov/connect/email-rand",
+        "fax": "202-228-1373",
+        "office": "167 Russell Senate Office Building",
+        "rss_url": "http://paul.senate.gov/rss/?p=news"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "P000603",
+        "birthday": "1963-01-07",
+        "cspanid": 9265241,
+        "firstname": "Rand",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Paul",
+        "link": "https://www.govtrack.us/congress/members/rand_paul/412492",
+        "middlename": "",
+        "name": "Sen. Rand Paul [R-KY]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00030836",
+        "pvsid": "117285",
+        "sortname": "Paul, Rand (Sen.) [R-KY]",
+        "twitterid": "RandPaul",
+        "youtubeid": "SenatorRandPaul"
+    },
+    "phone": "202-224-4343",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "KY",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.paul.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for North Dakota",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "338 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "http://www.hoeven.senate.gov/public/index.cfm/email-the-senator",
+        "fax": "202-224-7999",
+        "office": "338 Russell Senate Office Building",
+        "rss_url": "http://www.hoeven.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "H001061",
+        "birthday": "1957-03-13",
+        "cspanid": 85233,
+        "firstname": "John",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Hoeven",
+        "link": "https://www.govtrack.us/congress/members/john_hoeven/412494",
+        "middlename": "",
+        "name": "Sen. John Hoeven [R-ND]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00031688",
+        "pvsid": "41788",
+        "sortname": "Hoeven, John (Sen.) [R-ND]",
+        "twitterid": "SenJohnHoeven",
+        "youtubeid": "senatorjohnhoevennd"
+    },
+    "phone": "202-224-2551",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "ND",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.hoeven.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Utah",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "361A Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.lee.senate.gov/public/index.cfm/contact",
+        "office": "361a Russell Senate Office Building",
+        "rss_url": "http://www.lee.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "L000577",
+        "birthday": "1971-06-04",
+        "cspanid": 9267977,
+        "firstname": "Mike",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Lee",
+        "link": "https://www.govtrack.us/congress/members/mike_lee/412495",
+        "middlename": "",
+        "name": "Sen. Mike Lee [R-UT]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00031696",
+        "pvsid": "66395",
+        "sortname": "Lee, Mike (Sen.) [R-UT]",
+        "twitterid": "SenMikeLee",
+        "youtubeid": "senatormikelee"
+    },
+    "phone": "202-224-5444",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "UT",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.lee.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Wisconsin",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "328 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.ronjohnson.senate.gov/public/index.cfm/email-the-senator",
+        "fax": "920-230-7262",
+        "office": "328 Hart Senate Office Building",
+        "rss_url": "http://www.ronjohnson.senate.gov/public/index.cfm/rss/feed"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "J000293",
+        "birthday": "1955-04-08",
+        "cspanid": 62835,
+        "firstname": "Ron",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Johnson",
+        "link": "https://www.govtrack.us/congress/members/ron_johnson/412496",
+        "middlename": "",
+        "name": "Sen. Ron Johnson [R-WI]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00032546",
+        "pvsid": "126217",
+        "sortname": "Johnson, Ron (Sen.) [R-WI]",
+        "twitterid": "SenRonJohnson",
+        "youtubeid": "SenatorRonJohnson"
+    },
+    "phone": "202-224-5323",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "WI",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.ronjohnson.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Senior Senator for Hawaii",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "722 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.schatz.senate.gov/contact",
+        "fax": "202-228-1153",
+        "office": "722 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "S001194",
+        "birthday": "1972-10-20",
+        "cspanid": 87784,
+        "firstname": "Brian",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Schatz",
+        "link": "https://www.govtrack.us/congress/members/brian_schatz/412507",
+        "middlename": "Emanuel",
+        "name": "Sen. Brian Schatz [D-HI]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00028138",
+        "pvsid": "17852",
+        "sortname": "Schatz, Brian (Sen.) [D-HI]",
+        "twitterid": "SenBrianSchatz",
+        "youtubeid": "senbrianschatz"
+    },
+    "phone": "202-224-3934",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "senior",
+    "senator_rank_label": "Senior",
+    "startdate": "2017-01-03",
+    "state": "HI",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.schatz.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Illinois",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "524 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.duckworth.senate.gov/content/contact-senator",
+        "office": "524 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "D000622",
+        "birthday": "1968-03-12",
+        "cspanid": 94484,
+        "firstname": "Tammy",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Duckworth",
+        "link": "https://www.govtrack.us/congress/members/tammy_duckworth/412533",
+        "middlename": "",
+        "name": "Sen. Tammy Duckworth [D-IL]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00027860",
+        "pvsid": "57442",
+        "sortname": "Duckworth, Tammy (Sen.) [D-IL]",
+        "twitterid": "SenDuckworth",
+        "youtubeid": "repduckworth"
+    },
+    "phone": "202-224-2854",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "IL",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.duckworth.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for California",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "112 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.harris.senate.gov/contact",
+        "office": "112 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "H001075",
+        "birthday": "1964-10-20",
+        "cspanid": 1018696,
+        "firstname": "Kamala",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Harris",
+        "link": "https://www.govtrack.us/congress/members/kamala_harris/412678",
+        "middlename": "",
+        "name": "Sen. Kamala Harris [D-CA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00036915",
+        "pvsid": "120012",
+        "sortname": "Harris, Kamala (Sen.) [D-CA]",
+        "twitterid": "SenKamalaHarris",
+        "youtubeid": null
+    },
+    "phone": "202-224-3553",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "CA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.harris.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Louisiana",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "383 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.kennedy.senate.gov/public/email-me",
+        "office": "383 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "K000393",
+        "birthday": "1951-11-21",
+        "cspanid": 1011723,
+        "firstname": "John",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Kennedy",
+        "link": "https://www.govtrack.us/congress/members/john_kennedy/412679",
+        "middlename": "Neely",
+        "name": "Sen. John Kennedy [R-LA]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00026823",
+        "pvsid": "35496",
+        "sortname": "Kennedy, John (Sen.) [R-LA]",
+        "twitterid": "SenJohnKennedy",
+        "youtubeid": null
+    },
+    "phone": "202-224-4623",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "LA",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.kennedy.senate.gov/public"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for New Hampshire",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "330 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.hassan.senate.gov/content/contact-senator",
+        "office": "330 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "H001076",
+        "birthday": "1958-02-27",
+        "cspanid": 67481,
+        "firstname": "Margaret",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Hassan",
+        "link": "https://www.govtrack.us/congress/members/margaret_hassan/412680",
+        "middlename": "Wood",
+        "name": "Sen. Margaret “Maggie” Hassan [D-NH]",
+        "namemod": "",
+        "nickname": "Maggie",
+        "osid": "N00038397",
+        "pvsid": "42552",
+        "sortname": "Hassan, Margaret “Maggie” (Sen.) [D-NH]",
+        "twitterid": "Senatorhassan",
+        "youtubeid": null
+    },
+    "phone": "202-224-3324",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "NH",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.hassan.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116,
+        117
+    ],
+    "current": true,
+    "description": "Junior Senator for Nevada",
+    "district": null,
+    "enddate": "2023-01-03",
+    "extra": {
+        "address": "204 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.cortezmasto.senate.gov/contact",
+        "office": "204 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "C001113",
+        "birthday": "1964-03-29",
+        "cspanid": 105698,
+        "firstname": "Catherine",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Cortez Masto",
+        "link": "https://www.govtrack.us/congress/members/catherine_cortez_masto/412681",
+        "middlename": "",
+        "name": "Sen. Catherine Cortez Masto [D-NV]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00037161",
+        "pvsid": "69579",
+        "sortname": "Cortez Masto, Catherine (Sen.) [D-NV]",
+        "twitterid": "sencortezmasto",
+        "youtubeid": null
+    },
+    "phone": "202-224-3542",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2017-01-03",
+    "state": "NV",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.cortezmasto.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Alabama",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "326 Russell Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.jones.senate.gov/content/contact-senator",
+        "office": "326 Russell Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "J000300",
+        "birthday": "1954-05-04",
+        "cspanid": 1024592,
+        "firstname": "Doug",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Jones",
+        "link": "https://www.govtrack.us/congress/members/doug_jones/412741",
+        "middlename": "",
+        "name": "Sen. Doug Jones [D-AL]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00024817",
+        "pvsid": "176464",
+        "sortname": "Jones, Doug (Sen.) [D-AL]",
+        "twitterid": "sendougjones",
+        "youtubeid": null
+    },
+    "phone": "202-224-4124",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2018-01-03",
+    "state": "AL",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.jones.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115
+    ],
+    "current": true,
+    "description": "Junior Senator for Mississippi",
+    "district": null,
+    "enddate": "2018-11-27",
+    "extra": {
+        "address": "G12 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.hydesmith.senate.gov/content/contact-senator",
+        "end-type": "special-election",
+        "how": "appointment",
+        "office": "G12 Dirksen Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "H001079",
+        "birthday": "1959-05-10",
+        "cspanid": null,
+        "firstname": "Cindy",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Hyde-Smith",
+        "link": "https://www.govtrack.us/congress/members/cindy_hyde_smith/412743",
+        "middlename": "",
+        "name": "Sen. Cindy Hyde-Smith [R-MS]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00043298",
+        "pvsid": null,
+        "sortname": "Hyde-Smith, Cindy (Sen.) [R-MS]",
+        "twitterid": "SenHydeSmith",
+        "youtubeid": null
+    },
+    "phone": "202-224-5054",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2018-04-09",
+    "state": "MS",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.hydesmith.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Arizona",
+    "district": null,
+    "enddate": "2020-11-03",
+    "extra": {
+        "address": "G12 Dirksen Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.kyl.senate.gov/content/contact-senator-kyl",
+        "end-type": "special-election",
+        "fax": "202-228-2862",
+        "how": "appointment",
+        "office": "G12 Dirksen Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Republican",
+    "person": {
+        "bioguideid": "K000352",
+        "birthday": "1942-04-25",
+        "cspanid": null,
+        "firstname": "Jon",
+        "gender": "male",
+        "gender_label": "Male",
+        "lastname": "Kyl",
+        "link": "https://www.govtrack.us/congress/members/jon_kyl/300062",
+        "middlename": "",
+        "name": "Sen. Jon Kyl [R-AZ]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00006406",
+        "pvsid": "26721",
+        "sortname": "Kyl, Jon (Sen.) [R-AZ]",
+        "twitterid": null,
+        "youtubeid": null
+    },
+    "phone": "202-224-2235",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class3",
+    "senator_class_label": "Class 3",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2018-09-05",
+    "state": "AZ",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.kyl.senate.gov"
+},
+{
+    "caucus": null,
+    "congress_numbers": [
+        115,
+        116
+    ],
+    "current": true,
+    "description": "Junior Senator for Minnesota",
+    "district": null,
+    "enddate": "2021-01-03",
+    "extra": {
+        "address": "309 Hart Senate Office Building Washington DC 20510",
+        "contact_form": "https://www.smith.senate.gov/content/contact-senator",
+        "office": "309 Hart Senate Office Building"
+    },
+    "leadership_title": null,
+    "party": "Democrat",
+    "person": {
+        "bioguideid": "S001203",
+        "birthday": "1958-03-04",
+        "cspanid": 111313,
+        "firstname": "Tina",
+        "gender": "female",
+        "gender_label": "Female",
+        "lastname": "Smith",
+        "link": "https://www.govtrack.us/congress/members/tina_smith/412742",
+        "middlename": "Flint",
+        "name": "Sen. Tina Smith [D-MN]",
+        "namemod": "",
+        "nickname": "",
+        "osid": "N00042353",
+        "pvsid": "152968",
+        "sortname": "Smith, Tina (Sen.) [D-MN]",
+        "twitterid": "SenTinaSmith",
+        "youtubeid": null
+    },
+    "phone": "202-224-5641",
+    "role_type": "senator",
+    "role_type_label": "Senator",
+    "senator_class": "class2",
+    "senator_class_label": "Class 2",
+    "senator_rank": "junior",
+    "senator_rank_label": "Junior",
+    "startdate": "2018-11-07",
+    "state": "MN",
+    "title": "Sen.",
+    "title_long": "Senator",
+    "website": "https://www.smith.senate.gov"
+}
\ No newline at end of file
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/select_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/select_object.go
new file mode 100644 (file)
index 0000000..7b1c126
--- /dev/null
@@ -0,0 +1,203 @@
+package sample
+
+import (
+       "fmt"
+       "io/ioutil"
+       
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// SelectObjectSample shows how to get data from csv/json object by sql
+func SelectObjectSample() {
+       // Create a bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       //
+       // Create a Csv object
+       //
+       err = bucket.PutObjectFromFile(objectKey, localCsvFile)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create Csv Meta
+       csvMeta := oss.CsvMetaRequest{}
+       ret, err := bucket.CreateSelectCsvObjectMeta(objectKey, csvMeta)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("csv file meta:", ret)
+
+       // case 1: Isn't NULL
+       selReq := oss.SelectRequest{}
+       selReq.Expression = "select Year, StateAbbr, CityName, PopulationCount from ossobject where CityName != ''"
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+
+       body, err := bucket.SelectObject(objectKey, selReq)
+       if err != nil {
+               HandleError(err)
+       }
+       defer body.Close()
+
+       databyte, err := ioutil.ReadAll(body)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("some data in SelectCSVObject result:", string(databyte[:9]))
+
+       // case 2: Like
+       selReq = oss.SelectRequest{}
+       selReq.Expression =  "select Year, StateAbbr, CityName, Short_Question_Text from ossobject where Measure like '%blood pressure%Years'"
+       selReq.InputSerializationSelect.CsvBodyInput.FileHeaderInfo = "Use"
+       body, err = bucket.SelectObject(objectKey, selReq)
+       if err != nil {
+               HandleError(err)
+       }
+       defer body.Close()
+
+       databyte, err = ioutil.ReadAll(body)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("some data in SelectCSVObject result:", string(databyte[:9]))
+
+       // delete object
+       err = bucket.DeleteObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       //
+       // Create a LINES json object
+       //
+       err = bucket.PutObjectFromFile(objectKey, localJSONLinesFile)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Create LINES JSON Meta
+       jsonMeta := oss.JsonMetaRequest{
+               InputSerialization: oss.InputSerialization {
+                       JSON: oss.JSON {
+                               JSONType:"LINES",
+                       },
+               },
+       }
+       restSt, err := bucket.CreateSelectJsonObjectMeta(objectKey, jsonMeta)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("csv json meta:", restSt)
+
+       // case 1: sql where A=B
+       selReq = oss.SelectRequest{}
+       selReq.Expression = "select * from ossobject where party = 'Democrat'"
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "LINES"
+
+       body, err = bucket.SelectObject(objectKey, selReq)
+       if err != nil {
+               HandleError(err)
+       }
+       defer body.Close()
+
+       databyte, err = ioutil.ReadAll(body)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("some data in SelectJsonObject result:", string(databyte[:9]))
+
+       // case 2: LIKE
+       selReq = oss.SelectRequest{}
+       selReq.Expression = "select person.firstname, person.lastname from ossobject where person.birthday like '1959%'"
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "LINES"
+
+       body, err = bucket.SelectObject(objectKey, selReq)
+       if err != nil {
+               HandleError(err)
+       }
+       defer body.Close()
+
+       databyte, err = ioutil.ReadAll(body)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("some data in SelectJsonObject result:", string(databyte[:9]))
+
+       // delete object
+       err = bucket.DeleteObject(objectKey)
+       if err != nil {
+               HandleError(err)
+       }
+
+       //
+       // Create a Document json object
+       //
+       err = bucket.PutObjectFromFile(objectKey, localJSONFile)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // case 1: int avg, max, min 
+       selReq = oss.SelectRequest{}
+       selReq.Expression = `
+       select 
+               avg(cast(person.cspanid as int)), max(cast(person.cspanid as int)), 
+               min(cast(person.cspanid as int)) 
+       from 
+               ossobject.objects[*] 
+       where 
+               person.cspanid = 1011723
+       `
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "Document"
+       
+       body, err = bucket.SelectObject(objectKey, selReq)
+       if err != nil {
+               HandleError(err)
+       }
+       defer body.Close()
+
+       databyte, err = ioutil.ReadAll(body)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("data:", string(databyte))
+
+       // case 2: Concat
+       selReq = oss.SelectRequest{}
+       selReq.Expression = `
+       select 
+               person 
+       from 
+               ossobject.objects[*] 
+       where 
+               (person.firstname || person.lastname) = 'JohnKennedy'
+       `
+       selReq.OutputSerializationSelect.JsonBodyOutput.RecordDelimiter = ","
+       selReq.InputSerializationSelect.JsonBodyInput.JSONType = "Document"
+       
+       body, err = bucket.SelectObject(objectKey, selReq)
+       if err != nil {
+               HandleError(err)
+       }
+       defer body.Close()
+
+       databyte, err = ioutil.ReadAll(body)
+       if err != nil {
+               HandleError(err)
+       }
+       fmt.Println("some data in SelectJsonObject result:", string(databyte[:9]))
+       
+       // Delete the object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("SelectObjectSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sign_url.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sign_url.go
new file mode 100755 (executable)
index 0000000..8a73f4f
--- /dev/null
@@ -0,0 +1,80 @@
+package sample
+
+import (
+       "fmt"
+       "io/ioutil"
+       "os"
+       "strings"
+
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// SignURLSample signs URL sample
+func SignURLSample() {
+       // Create bucket
+       bucket, err := GetTestBucket(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Put object
+       signedURL, err := bucket.SignURL(objectKey, oss.HTTPPut, 60)
+       if err != nil {
+               HandleError(err)
+       }
+
+       var val = "花间一壶酒,独酌无相亲。 举杯邀明月,对影成三人。"
+       err = bucket.PutObjectWithURL(signedURL, strings.NewReader(val))
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Put object with option
+       options := []oss.Option{
+               oss.Meta("myprop", "mypropval"),
+               oss.ContentType("image/tiff"),
+       }
+
+       signedURL, err = bucket.SignURL(objectKey, oss.HTTPPut, 60, options...)
+       if err != nil {
+               HandleError(err)
+       }
+
+       err = bucket.PutObjectFromFileWithURL(signedURL, localFile, options...)
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Get object
+       signedURL, err = bucket.SignURL(objectKey, oss.HTTPGet, 60)
+       if err != nil {
+               HandleError(err)
+       }
+
+       body, err := bucket.GetObjectWithURL(signedURL)
+       if err != nil {
+               HandleError(err)
+       }
+       defer body.Close()
+
+       // Read content
+       data, err := ioutil.ReadAll(body)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+       fmt.Println("data:", string(data))
+
+       err = bucket.GetObjectToFileWithURL(signedURL, "mynewfile-1.jpg")
+       if err != nil {
+               HandleError(err)
+       }
+
+       // Delete the object and bucket
+       err = DeleteTestBucketAndObject(bucketName)
+       if err != nil {
+               HandleError(err)
+       }
+
+       fmt.Println("SignURLSample completed")
+}
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-crypto-cpp-rsa.jpg b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-crypto-cpp-rsa.jpg
new file mode 100644 (file)
index 0000000..e498969
Binary files /dev/null and b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-crypto-cpp-rsa.jpg differ
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-crypto-python-rsa.jpg b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-crypto-python-rsa.jpg
new file mode 100644 (file)
index 0000000..cfe6464
Binary files /dev/null and b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-crypto-python-rsa.jpg differ
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-src.jpg b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-src.jpg
new file mode 100644 (file)
index 0000000..ffd46a2
Binary files /dev/null and b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/test-client-encryption-src.jpg differ
diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample_crypto/sample_crypto.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample_crypto/sample_crypto.go
new file mode 100644 (file)
index 0000000..11f19f5
--- /dev/null
@@ -0,0 +1,304 @@
+package main
+
+import (
+       "bytes"
+       "fmt"
+       "io/ioutil"
+       "os"
+
+       kms "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
+       "github.com/aliyun/aliyun-oss-go-sdk/oss"
+       "github.com/aliyun/aliyun-oss-go-sdk/oss/crypto"
+)
+
+func SampleRsaNormalObject() {
+       // create oss client
+       client, err := oss.New("<yourEndpoint>", "<yourAccessKeyId>", "<yourAccessKeySecret>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // Create a description of the master key. Once created, it cannot be modified. The master key description and the master key are one-to-one correspondence.
+       // If all objects use the same master key, the master key description can also be empty, but subsequent replacement of the master key is not supported.
+       // Because if the description is empty, it is impossible to determine which master key is used when decrypting object.
+       // It is strongly recommended that: configure the master key description(json string) for each master key, and the client should save the correspondence between them.
+       // The server does not save their correspondence
+
+       // Map converted by the master key description information (json string)
+       materialDesc := make(map[string]string)
+       materialDesc["desc"] = "<your master encrypt key material describe information>"
+
+       // Create a master key object based on the master key description
+       masterRsaCipher, err := osscrypto.CreateMasterRsa(materialDesc, "<your rsa public key>", "<your rsa private key>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // Create an interface for encryption based on the master key object, encrypt using aec ctr mode
+       contentProvider := osscrypto.CreateAesCtrCipher(masterRsaCipher)
+
+       // Get a storage space for client encryption, the bucket has to be created
+       // Client-side encrypted buckets have similar usages to ordinary buckets.
+       cryptoBucket, err := osscrypto.GetCryptoBucket(client, "<yourBucketName>", contentProvider)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // put object ,will be automatically encrypted
+       err = cryptoBucket.PutObject("<yourObjectName>", bytes.NewReader([]byte("yourObjectValueByteArrary")))
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // get object ,will be automatically decrypted
+       body, err := cryptoBucket.GetObject("<yourObjectName>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+       defer body.Close()
+
+       data, err := ioutil.ReadAll(body)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+       fmt.Println("data:", string(data))
+}
+
+func SampleRsaMultiPartObject() {
+       // create oss client
+       client, err := oss.New("<yourEndpoint>", "<yourAccessKeyId>", "<yourAccessKeySecret>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // Create a description of the master key. Once created, it cannot be modified. The master key description and the master key are one-to-one correspondence.
+       // If all objects use the same master key, the master key description can also be empty, but subsequent replacement of the master key is not supported.
+       // Because if the description is empty, it is impossible to determine which master key is used when decrypting object.
+       // It is strongly recommended that: configure the master key description(json string) for each master key, and the client should save the correspondence between them.
+       // The server does not save their correspondence
+
+       // Map converted by the master key description information (json string)
+       materialDesc := make(map[string]string)
+       materialDesc["desc"] = "<your master encrypt key material describe information>"
+
+       // Create a master key object based on the master key description
+       masterRsaCipher, err := osscrypto.CreateMasterRsa(materialDesc, "<your rsa public key>", "<your rsa private key>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // Create an interface for encryption based on the master key object, encrypt using aec ctr mode
+       contentProvider := osscrypto.CreateAesCtrCipher(masterRsaCipher)
+
+       // Get a storage space for client encryption, the bucket has to be created
+       // Client-side encrypted buckets have similar usages to ordinary buckets.
+       cryptoBucket, err := osscrypto.GetCryptoBucket(client, "<yourBucketName>", contentProvider)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       fileName := "<yourLocalFilePath>"
+       fileInfo, err := os.Stat(fileName)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+       fileSize := fileInfo.Size()
+
+       // Encryption context information
+       var cryptoContext osscrypto.PartCryptoContext
+       cryptoContext.DataSize = fileSize
+
+       // The expected number of parts, the actual number of parts is subject to subsequent calculations.
+       expectPartCount := int64(10)
+
+       //Currently aes ctr encryption block size requires 16 byte alignment
+       cryptoContext.PartSize = (fileSize / expectPartCount / 16) * 16
+
+       imur, err := cryptoBucket.InitiateMultipartUpload("<yourObjectName>", &cryptoContext)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       var partsUpload []oss.UploadPart
+       for _, chunk := range chunks {
+               part, err := cryptoBucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number), cryptoContext)
+               if err != nil {
+                       fmt.Println("Error:", err)
+                       os.Exit(-1)
+               }
+               partsUpload = append(partsUpload, part)
+       }
+
+       // Complete
+       _, err = cryptoBucket.CompleteMultipartUpload(imur, partsUpload)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+}
+
+// Query the master key according to the master key description information.
+// If you need to decrypt different master key encryption objects, you need to provide this interface.
+type MockRsaManager struct {
+}
+
+func (mg *MockRsaManager) GetMasterKey(matDesc map[string]string) ([]string, error) {
+       // to do
+       keyList := []string{"<yourRsaPublicKey>", "<yourRsaPrivatKey>"}
+       return keyList, nil
+}
+
+// Decrypt the object encrypted by different master keys
+func SampleMultipleMasterRsa() {
+       // create oss client
+       client, err := oss.New("<yourEndpoint>", "<yourAccessKeyId>", "<yourAccessKeySecret>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // Create a description of the master key. Once created, it cannot be modified. The master key description and the master key are one-to-one correspondence.
+       // If all objects use the same master key, the master key description can also be empty, but subsequent replacement of the master key is not supported.
+       // Because if the description is empty, it is impossible to determine which master key is used when decrypting object.
+       // It is strongly recommended that: configure the master key description(json string) for each master key, and the client should save the correspondence between them.
+       // The server does not save their correspondence
+
+       // Map converted by the master key description information (json string)
+       materialDesc := make(map[string]string)
+       materialDesc["desc"] = "<your master encrypt key material describe information>"
+
+       // Create a master key object based on the master key description
+       masterRsaCipher, err := osscrypto.CreateMasterRsa(materialDesc, "<your rsa public key>", "<your rsa private key>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // Create an interface for encryption based on the master key object, encrypt using aec ctr mode
+       contentProvider := osscrypto.CreateAesCtrCipher(masterRsaCipher)
+
+       // If you need to decrypt objects encrypted by different ma keys, you need to provide this interface.
+       var mockRsaManager MockRsaManager
+       var options []osscrypto.CryptoBucketOption
+       options = append(options, osscrypto.SetMasterCipherManager(&mockRsaManager))
+
+       // Get a storage space for client encryption, the bucket has to be created
+       // Client-side encrypted buckets have similar usages to ordinary buckets.
+       cryptoBucket, err := osscrypto.GetCryptoBucket(client, "<yourBucketName>", contentProvider, options...)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // put object ,will be automatically encrypted
+       err = cryptoBucket.PutObject("<yourObjectName>", bytes.NewReader([]byte("yourObjectValueByteArrary")))
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // get object ,will be automatically decrypted
+       body, err := cryptoBucket.GetObject("<otherObjectNameEncryptedWithOtherRsa>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+       defer body.Close()
+
+       data, err := ioutil.ReadAll(body)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+       fmt.Println("data:", string(data))
+}
+
+func SampleKmsNormalObject() {
+       // create oss client
+       client, err := oss.New("<yourEndpoint>", "<yourAccessKeyId>", "<yourAccessKeySecret>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // create kms client
+       kmsClient, err := kms.NewClientWithAccessKey("<yourKmsRegion>", "<yourKmsAccessKeyId>", "<yourKmsAccessKeySecret>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // Create a description of the master key. Once created, it cannot be modified. The master key description and the master key are one-to-one correspondence.
+       // If all objects use the same master key, the master key description can also be empty, but subsequent replacement of the master key is not supported.
+       // Because if the description is empty, it is impossible to determine which master key is used when decrypting object.
+       // It is strongly recommended that: configure the master key description(json string) for each master key, and the client should save the correspondence between them.
+       // The server does not save their correspondence
+
+       // Map converted by the master key description information (json string)
+       materialDesc := make(map[string]string)
+       materialDesc["desc"] = "<your kms encrypt key material describe information>"
+
+       // Create a master key object based on the master key description
+       masterkmsCipher, err := osscrypto.CreateMasterAliKms(materialDesc, "<YourKmsId>", kmsClient)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // Create an interface for encryption based on the master key object, encrypt using aec ctr mode
+       contentProvider := osscrypto.CreateAesCtrCipher(masterkmsCipher)
+
+       // Get a storage space for client encryption, the bucket has to be created
+       // Client-side encrypted buckets have similar usages to ordinary buckets.
+       cryptoBucket, err := osscrypto.GetCryptoBucket(client, "<yourBucketName>", contentProvider)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // put object ,will be automatically encrypted
+       err = cryptoBucket.PutObject("<yourObjectName>", bytes.NewReader([]byte("yourObjectValueByteArrary")))
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+
+       // get object ,will be automatically decrypted
+       body, err := cryptoBucket.GetObject("<yourObjectName>")
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+       defer body.Close()
+
+       data, err := ioutil.ReadAll(body)
+       if err != nil {
+               fmt.Println("Error:", err)
+               os.Exit(-1)
+       }
+       fmt.Println("data:", string(data))
+}
+
+func main() {
+       SampleRsaNormalObject()
+       SampleRsaMultiPartObject()
+       SampleMultipleMasterRsa()
+       SampleKmsNormalObject()
+}
diff --git a/vendor/golang.org/x/time/AUTHORS b/vendor/golang.org/x/time/AUTHORS
new file mode 100644 (file)
index 0000000..15167cd
--- /dev/null
@@ -0,0 +1,3 @@
+# This source code refers to The Go Authors for copyright purposes.
+# The master list of authors is in the main Go distribution,
+# visible at http://tip.golang.org/AUTHORS.
diff --git a/vendor/golang.org/x/time/CONTRIBUTING.md b/vendor/golang.org/x/time/CONTRIBUTING.md
new file mode 100644 (file)
index 0000000..d0485e8
--- /dev/null
@@ -0,0 +1,26 @@
+# Contributing to Go
+
+Go is an open source project.
+
+It is the work of hundreds of contributors. We appreciate your help!
+
+## Filing issues
+
+When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
+
+1.  What version of Go are you using (`go version`)?
+2.  What operating system and processor architecture are you using?
+3.  What did you do?
+4.  What did you expect to see?
+5.  What did you see instead?
+
+General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
+The gophers there will answer or ask you to file an issue if you've tripped over a bug.
+
+## Contributing code
+
+Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
+before sending patches.
+
+Unless otherwise noted, the Go source files are distributed under
+the BSD-style license found in the LICENSE file.
diff --git a/vendor/golang.org/x/time/CONTRIBUTORS b/vendor/golang.org/x/time/CONTRIBUTORS
new file mode 100644 (file)
index 0000000..1c4577e
--- /dev/null
@@ -0,0 +1,3 @@
+# This source code was written by the Go contributors.
+# The master list of contributors is in the main Go distribution,
+# visible at http://tip.golang.org/CONTRIBUTORS.
diff --git a/vendor/golang.org/x/time/LICENSE b/vendor/golang.org/x/time/LICENSE
new file mode 100644 (file)
index 0000000..6a66aea
--- /dev/null
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/golang.org/x/time/PATENTS b/vendor/golang.org/x/time/PATENTS
new file mode 100644 (file)
index 0000000..7330990
--- /dev/null
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Go project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Go, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Go.  This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation.  If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Go or any code incorporated within this
+implementation of Go constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Go
+shall terminate as of the date such litigation is filed.
diff --git a/vendor/golang.org/x/time/README.md b/vendor/golang.org/x/time/README.md
new file mode 100644 (file)
index 0000000..705a497
--- /dev/null
@@ -0,0 +1,19 @@
+# Go Time
+
+[![Go Reference](https://pkg.go.dev/badge/golang.org/x/time.svg)](https://pkg.go.dev/golang.org/x/time)
+
+This repository provides supplementary Go time packages.
+
+## Download/Install
+
+The easiest way to install is to run `go get -u golang.org/x/time`. You can
+also manually git clone the repository to `$GOPATH/src/golang.org/x/time`.
+
+## Report Issues / Send Patches
+
+This repository uses Gerrit for code changes. To learn how to submit changes to
+this repository, see https://golang.org/doc/contribute.html.
+
+The main issue tracker for the time repository is located at
+https://github.com/golang/go/issues. Prefix your issue with "x/time:" in the
+subject line, so it is easy to find.
diff --git a/vendor/golang.org/x/time/go.mod b/vendor/golang.org/x/time/go.mod
new file mode 100644 (file)
index 0000000..46ac917
--- /dev/null
@@ -0,0 +1 @@
+module golang.org/x/time
diff --git a/vendor/golang.org/x/time/rate/rate.go b/vendor/golang.org/x/time/rate/rate.go
new file mode 100644 (file)
index 0000000..a98fe77
--- /dev/null
@@ -0,0 +1,402 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package rate provides a rate limiter.
+package rate
+
+import (
+       "context"
+       "fmt"
+       "math"
+       "sync"
+       "time"
+)
+
+// Limit defines the maximum frequency of some events.
+// Limit is represented as number of events per second.
+// A zero Limit allows no events.
+type Limit float64
+
+// Inf is the infinite rate limit; it allows all events (even if burst is zero).
+const Inf = Limit(math.MaxFloat64)
+
+// Every converts a minimum time interval between events to a Limit.
+func Every(interval time.Duration) Limit {
+       if interval <= 0 {
+               return Inf
+       }
+       return 1 / Limit(interval.Seconds())
+}
+
+// A Limiter controls how frequently events are allowed to happen.
+// It implements a "token bucket" of size b, initially full and refilled
+// at rate r tokens per second.
+// Informally, in any large enough time interval, the Limiter limits the
+// rate to r tokens per second, with a maximum burst size of b events.
+// As a special case, if r == Inf (the infinite rate), b is ignored.
+// See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets.
+//
+// The zero value is a valid Limiter, but it will reject all events.
+// Use NewLimiter to create non-zero Limiters.
+//
+// Limiter has three main methods, Allow, Reserve, and Wait.
+// Most callers should use Wait.
+//
+// Each of the three methods consumes a single token.
+// They differ in their behavior when no token is available.
+// If no token is available, Allow returns false.
+// If no token is available, Reserve returns a reservation for a future token
+// and the amount of time the caller must wait before using it.
+// If no token is available, Wait blocks until one can be obtained
+// or its associated context.Context is canceled.
+//
+// The methods AllowN, ReserveN, and WaitN consume n tokens.
+type Limiter struct {
+       mu     sync.Mutex
+       limit  Limit
+       burst  int
+       tokens float64
+       // last is the last time the limiter's tokens field was updated
+       last time.Time
+       // lastEvent is the latest time of a rate-limited event (past or future)
+       lastEvent time.Time
+}
+
+// Limit returns the maximum overall event rate.
+func (lim *Limiter) Limit() Limit {
+       lim.mu.Lock()
+       defer lim.mu.Unlock()
+       return lim.limit
+}
+
+// Burst returns the maximum burst size. Burst is the maximum number of tokens
+// that can be consumed in a single call to Allow, Reserve, or Wait, so higher
+// Burst values allow more events to happen at once.
+// A zero Burst allows no events, unless limit == Inf.
+func (lim *Limiter) Burst() int {
+       lim.mu.Lock()
+       defer lim.mu.Unlock()
+       return lim.burst
+}
+
+// NewLimiter returns a new Limiter that allows events up to rate r and permits
+// bursts of at most b tokens.
+func NewLimiter(r Limit, b int) *Limiter {
+       return &Limiter{
+               limit: r,
+               burst: b,
+       }
+}
+
+// Allow is shorthand for AllowN(time.Now(), 1).
+func (lim *Limiter) Allow() bool {
+       return lim.AllowN(time.Now(), 1)
+}
+
+// AllowN reports whether n events may happen at time now.
+// Use this method if you intend to drop / skip events that exceed the rate limit.
+// Otherwise use Reserve or Wait.
+func (lim *Limiter) AllowN(now time.Time, n int) bool {
+       return lim.reserveN(now, n, 0).ok
+}
+
+// A Reservation holds information about events that are permitted by a Limiter to happen after a delay.
+// A Reservation may be canceled, which may enable the Limiter to permit additional events.
+type Reservation struct {
+       ok        bool
+       lim       *Limiter
+       tokens    int
+       timeToAct time.Time
+       // This is the Limit at reservation time, it can change later.
+       limit Limit
+}
+
+// OK returns whether the limiter can provide the requested number of tokens
+// within the maximum wait time.  If OK is false, Delay returns InfDuration, and
+// Cancel does nothing.
+func (r *Reservation) OK() bool {
+       return r.ok
+}
+
+// Delay is shorthand for DelayFrom(time.Now()).
+func (r *Reservation) Delay() time.Duration {
+       return r.DelayFrom(time.Now())
+}
+
+// InfDuration is the duration returned by Delay when a Reservation is not OK.
+const InfDuration = time.Duration(1<<63 - 1)
+
+// DelayFrom returns the duration for which the reservation holder must wait
+// before taking the reserved action.  Zero duration means act immediately.
+// InfDuration means the limiter cannot grant the tokens requested in this
+// Reservation within the maximum wait time.
+func (r *Reservation) DelayFrom(now time.Time) time.Duration {
+       if !r.ok {
+               return InfDuration
+       }
+       delay := r.timeToAct.Sub(now)
+       if delay < 0 {
+               return 0
+       }
+       return delay
+}
+
+// Cancel is shorthand for CancelAt(time.Now()).
+func (r *Reservation) Cancel() {
+       r.CancelAt(time.Now())
+       return
+}
+
+// CancelAt indicates that the reservation holder will not perform the reserved action
+// and reverses the effects of this Reservation on the rate limit as much as possible,
+// considering that other reservations may have already been made.
+func (r *Reservation) CancelAt(now time.Time) {
+       if !r.ok {
+               return
+       }
+
+       r.lim.mu.Lock()
+       defer r.lim.mu.Unlock()
+
+       if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(now) {
+               return
+       }
+
+       // calculate tokens to restore
+       // The duration between lim.lastEvent and r.timeToAct tells us how many tokens were reserved
+       // after r was obtained. These tokens should not be restored.
+       restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct))
+       if restoreTokens <= 0 {
+               return
+       }
+       // advance time to now
+       now, _, tokens := r.lim.advance(now)
+       // calculate new number of tokens
+       tokens += restoreTokens
+       if burst := float64(r.lim.burst); tokens > burst {
+               tokens = burst
+       }
+       // update state
+       r.lim.last = now
+       r.lim.tokens = tokens
+       if r.timeToAct == r.lim.lastEvent {
+               prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens)))
+               if !prevEvent.Before(now) {
+                       r.lim.lastEvent = prevEvent
+               }
+       }
+
+       return
+}
+
+// Reserve is shorthand for ReserveN(time.Now(), 1).
+func (lim *Limiter) Reserve() *Reservation {
+       return lim.ReserveN(time.Now(), 1)
+}
+
+// ReserveN returns a Reservation that indicates how long the caller must wait before n events happen.
+// The Limiter takes this Reservation into account when allowing future events.
+// The returned Reservation’s OK() method returns false if n exceeds the Limiter's burst size.
+// Usage example:
+//   r := lim.ReserveN(time.Now(), 1)
+//   if !r.OK() {
+//     // Not allowed to act! Did you remember to set lim.burst to be > 0 ?
+//     return
+//   }
+//   time.Sleep(r.Delay())
+//   Act()
+// Use this method if you wish to wait and slow down in accordance with the rate limit without dropping events.
+// If you need to respect a deadline or cancel the delay, use Wait instead.
+// To drop or skip events exceeding rate limit, use Allow instead.
+func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation {
+       r := lim.reserveN(now, n, InfDuration)
+       return &r
+}
+
+// Wait is shorthand for WaitN(ctx, 1).
+func (lim *Limiter) Wait(ctx context.Context) (err error) {
+       return lim.WaitN(ctx, 1)
+}
+
+// WaitN blocks until lim permits n events to happen.
+// It returns an error if n exceeds the Limiter's burst size, the Context is
+// canceled, or the expected wait time exceeds the Context's Deadline.
+// The burst limit is ignored if the rate limit is Inf.
+func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) {
+       lim.mu.Lock()
+       burst := lim.burst
+       limit := lim.limit
+       lim.mu.Unlock()
+
+       if n > burst && limit != Inf {
+               return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, burst)
+       }
+       // Check if ctx is already cancelled
+       select {
+       case <-ctx.Done():
+               return ctx.Err()
+       default:
+       }
+       // Determine wait limit
+       now := time.Now()
+       waitLimit := InfDuration
+       if deadline, ok := ctx.Deadline(); ok {
+               waitLimit = deadline.Sub(now)
+       }
+       // Reserve
+       r := lim.reserveN(now, n, waitLimit)
+       if !r.ok {
+               return fmt.Errorf("rate: Wait(n=%d) would exceed context deadline", n)
+       }
+       // Wait if necessary
+       delay := r.DelayFrom(now)
+       if delay == 0 {
+               return nil
+       }
+       t := time.NewTimer(delay)
+       defer t.Stop()
+       select {
+       case <-t.C:
+               // We can proceed.
+               return nil
+       case <-ctx.Done():
+               // Context was canceled before we could proceed.  Cancel the
+               // reservation, which may permit other events to proceed sooner.
+               r.Cancel()
+               return ctx.Err()
+       }
+}
+
+// SetLimit is shorthand for SetLimitAt(time.Now(), newLimit).
+func (lim *Limiter) SetLimit(newLimit Limit) {
+       lim.SetLimitAt(time.Now(), newLimit)
+}
+
+// SetLimitAt sets a new Limit for the limiter. The new Limit, and Burst, may be violated
+// or underutilized by those which reserved (using Reserve or Wait) but did not yet act
+// before SetLimitAt was called.
+func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit) {
+       lim.mu.Lock()
+       defer lim.mu.Unlock()
+
+       now, _, tokens := lim.advance(now)
+
+       lim.last = now
+       lim.tokens = tokens
+       lim.limit = newLimit
+}
+
+// SetBurst is shorthand for SetBurstAt(time.Now(), newBurst).
+func (lim *Limiter) SetBurst(newBurst int) {
+       lim.SetBurstAt(time.Now(), newBurst)
+}
+
+// SetBurstAt sets a new burst size for the limiter.
+func (lim *Limiter) SetBurstAt(now time.Time, newBurst int) {
+       lim.mu.Lock()
+       defer lim.mu.Unlock()
+
+       now, _, tokens := lim.advance(now)
+
+       lim.last = now
+       lim.tokens = tokens
+       lim.burst = newBurst
+}
+
+// reserveN is a helper method for AllowN, ReserveN, and WaitN.
+// maxFutureReserve specifies the maximum reservation wait duration allowed.
+// reserveN returns Reservation, not *Reservation, to avoid allocation in AllowN and WaitN.
+func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
+       lim.mu.Lock()
+
+       if lim.limit == Inf {
+               lim.mu.Unlock()
+               return Reservation{
+                       ok:        true,
+                       lim:       lim,
+                       tokens:    n,
+                       timeToAct: now,
+               }
+       }
+
+       now, last, tokens := lim.advance(now)
+
+       // Calculate the remaining number of tokens resulting from the request.
+       tokens -= float64(n)
+
+       // Calculate the wait duration
+       var waitDuration time.Duration
+       if tokens < 0 {
+               waitDuration = lim.limit.durationFromTokens(-tokens)
+       }
+
+       // Decide result
+       ok := n <= lim.burst && waitDuration <= maxFutureReserve
+
+       // Prepare reservation
+       r := Reservation{
+               ok:    ok,
+               lim:   lim,
+               limit: lim.limit,
+       }
+       if ok {
+               r.tokens = n
+               r.timeToAct = now.Add(waitDuration)
+       }
+
+       // Update state
+       if ok {
+               lim.last = now
+               lim.tokens = tokens
+               lim.lastEvent = r.timeToAct
+       } else {
+               lim.last = last
+       }
+
+       lim.mu.Unlock()
+       return r
+}
+
+// advance calculates and returns an updated state for lim resulting from the passage of time.
+// lim is not changed.
+// advance requires that lim.mu is held.
+func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
+       last := lim.last
+       if now.Before(last) {
+               last = now
+       }
+
+       // Avoid making delta overflow below when last is very old.
+       maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
+       elapsed := now.Sub(last)
+       if elapsed > maxElapsed {
+               elapsed = maxElapsed
+       }
+
+       // Calculate the new number of tokens, due to time that passed.
+       delta := lim.limit.tokensFromDuration(elapsed)
+       tokens := lim.tokens + delta
+       if burst := float64(lim.burst); tokens > burst {
+               tokens = burst
+       }
+
+       return now, last, tokens
+}
+
+// durationFromTokens is a unit conversion function from the number of tokens to the duration
+// of time it takes to accumulate them at a rate of limit tokens per second.
+func (limit Limit) durationFromTokens(tokens float64) time.Duration {
+       seconds := tokens / float64(limit)
+       return time.Nanosecond * time.Duration(1e9*seconds)
+}
+
+// tokensFromDuration is a unit conversion function from a time duration to the number of tokens
+// which could be accumulated during that duration at a rate of limit tokens per second.
+func (limit Limit) tokensFromDuration(d time.Duration) float64 {
+       // Split the integer and fractional parts ourself to minimize rounding errors.
+       // See golang.org/issues/34861.
+       sec := float64(d/time.Second) * float64(limit)
+       nsec := float64(d%time.Second) * float64(limit)
+       return sec + nsec/1e9
+}
diff --git a/vendor/golang.org/x/time/rate/rate_test.go b/vendor/golang.org/x/time/rate/rate_test.go
new file mode 100644 (file)
index 0000000..b0ed34d
--- /dev/null
@@ -0,0 +1,477 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.7
+
+package rate
+
+import (
+       "context"
+       "math"
+       "runtime"
+       "sync"
+       "sync/atomic"
+       "testing"
+       "time"
+)
+
+func TestLimit(t *testing.T) {
+       if Limit(10) == Inf {
+               t.Errorf("Limit(10) == Inf should be false")
+       }
+}
+
+func closeEnough(a, b Limit) bool {
+       return (math.Abs(float64(a)/float64(b)) - 1.0) < 1e-9
+}
+
+func TestEvery(t *testing.T) {
+       cases := []struct {
+               interval time.Duration
+               lim      Limit
+       }{
+               {0, Inf},
+               {-1, Inf},
+               {1 * time.Nanosecond, Limit(1e9)},
+               {1 * time.Microsecond, Limit(1e6)},
+               {1 * time.Millisecond, Limit(1e3)},
+               {10 * time.Millisecond, Limit(100)},
+               {100 * time.Millisecond, Limit(10)},
+               {1 * time.Second, Limit(1)},
+               {2 * time.Second, Limit(0.5)},
+               {time.Duration(2.5 * float64(time.Second)), Limit(0.4)},
+               {4 * time.Second, Limit(0.25)},
+               {10 * time.Second, Limit(0.1)},
+               {time.Duration(math.MaxInt64), Limit(1e9 / float64(math.MaxInt64))},
+       }
+       for _, tc := range cases {
+               lim := Every(tc.interval)
+               if !closeEnough(lim, tc.lim) {
+                       t.Errorf("Every(%v) = %v want %v", tc.interval, lim, tc.lim)
+               }
+       }
+}
+
+const (
+       d = 100 * time.Millisecond
+)
+
+var (
+       t0 = time.Now()
+       t1 = t0.Add(time.Duration(1) * d)
+       t2 = t0.Add(time.Duration(2) * d)
+       t3 = t0.Add(time.Duration(3) * d)
+       t4 = t0.Add(time.Duration(4) * d)
+       t5 = t0.Add(time.Duration(5) * d)
+       t9 = t0.Add(time.Duration(9) * d)
+)
+
+type allow struct {
+       t  time.Time
+       n  int
+       ok bool
+}
+
+func run(t *testing.T, lim *Limiter, allows []allow) {
+       for i, allow := range allows {
+               ok := lim.AllowN(allow.t, allow.n)
+               if ok != allow.ok {
+                       t.Errorf("step %d: lim.AllowN(%v, %v) = %v want %v",
+                               i, allow.t, allow.n, ok, allow.ok)
+               }
+       }
+}
+
+func TestLimiterBurst1(t *testing.T) {
+       run(t, NewLimiter(10, 1), []allow{
+               {t0, 1, true},
+               {t0, 1, false},
+               {t0, 1, false},
+               {t1, 1, true},
+               {t1, 1, false},
+               {t1, 1, false},
+               {t2, 2, false}, // burst size is 1, so n=2 always fails
+               {t2, 1, true},
+               {t2, 1, false},
+       })
+}
+
+func TestLimiterBurst3(t *testing.T) {
+       run(t, NewLimiter(10, 3), []allow{
+               {t0, 2, true},
+               {t0, 2, false},
+               {t0, 1, true},
+               {t0, 1, false},
+               {t1, 4, false},
+               {t2, 1, true},
+               {t3, 1, true},
+               {t4, 1, true},
+               {t4, 1, true},
+               {t4, 1, false},
+               {t4, 1, false},
+               {t9, 3, true},
+               {t9, 0, true},
+       })
+}
+
+func TestLimiterJumpBackwards(t *testing.T) {
+       run(t, NewLimiter(10, 3), []allow{
+               {t1, 1, true}, // start at t1
+               {t0, 1, true}, // jump back to t0, two tokens remain
+               {t0, 1, true},
+               {t0, 1, false},
+               {t0, 1, false},
+               {t1, 1, true}, // got a token
+               {t1, 1, false},
+               {t1, 1, false},
+               {t2, 1, true}, // got another token
+               {t2, 1, false},
+               {t2, 1, false},
+       })
+}
+
+// Ensure that tokensFromDuration doesn't produce
+// rounding errors by truncating nanoseconds.
+// See golang.org/issues/34861.
+func TestLimiter_noTruncationErrors(t *testing.T) {
+       if !NewLimiter(0.7692307692307693, 1).Allow() {
+               t.Fatal("expected true")
+       }
+}
+
+func TestSimultaneousRequests(t *testing.T) {
+       const (
+               limit       = 1
+               burst       = 5
+               numRequests = 15
+       )
+       var (
+               wg    sync.WaitGroup
+               numOK = uint32(0)
+       )
+
+       // Very slow replenishing bucket.
+       lim := NewLimiter(limit, burst)
+
+       // Tries to take a token, atomically updates the counter and decreases the wait
+       // group counter.
+       f := func() {
+               defer wg.Done()
+               if ok := lim.Allow(); ok {
+                       atomic.AddUint32(&numOK, 1)
+               }
+       }
+
+       wg.Add(numRequests)
+       for i := 0; i < numRequests; i++ {
+               go f()
+       }
+       wg.Wait()
+       if numOK != burst {
+               t.Errorf("numOK = %d, want %d", numOK, burst)
+       }
+}
+
+func TestLongRunningQPS(t *testing.T) {
+       if testing.Short() {
+               t.Skip("skipping in short mode")
+       }
+       if runtime.GOOS == "openbsd" {
+               t.Skip("low resolution time.Sleep invalidates test (golang.org/issue/14183)")
+               return
+       }
+
+       // The test runs for a few seconds executing many requests and then checks
+       // that overall number of requests is reasonable.
+       const (
+               limit = 100
+               burst = 100
+       )
+       var numOK = int32(0)
+
+       lim := NewLimiter(limit, burst)
+
+       var wg sync.WaitGroup
+       f := func() {
+               if ok := lim.Allow(); ok {
+                       atomic.AddInt32(&numOK, 1)
+               }
+               wg.Done()
+       }
+
+       start := time.Now()
+       end := start.Add(5 * time.Second)
+       for time.Now().Before(end) {
+               wg.Add(1)
+               go f()
+
+               // This will still offer ~500 requests per second, but won't consume
+               // outrageous amount of CPU.
+               time.Sleep(2 * time.Millisecond)
+       }
+       wg.Wait()
+       elapsed := time.Since(start)
+       ideal := burst + (limit * float64(elapsed) / float64(time.Second))
+
+       // We should never get more requests than allowed.
+       if want := int32(ideal + 1); numOK > want {
+               t.Errorf("numOK = %d, want %d (ideal %f)", numOK, want, ideal)
+       }
+       // We should get very close to the number of requests allowed.
+       if want := int32(0.999 * ideal); numOK < want {
+               t.Errorf("numOK = %d, want %d (ideal %f)", numOK, want, ideal)
+       }
+}
+
+type request struct {
+       t   time.Time
+       n   int
+       act time.Time
+       ok  bool
+}
+
+// dFromDuration converts a duration to a multiple of the global constant d
+func dFromDuration(dur time.Duration) int {
+       // Adding a millisecond to be swallowed by the integer division
+       // because we don't care about small inaccuracies
+       return int((dur + time.Millisecond) / d)
+}
+
+// dSince returns multiples of d since t0
+func dSince(t time.Time) int {
+       return dFromDuration(t.Sub(t0))
+}
+
+func runReserve(t *testing.T, lim *Limiter, req request) *Reservation {
+       return runReserveMax(t, lim, req, InfDuration)
+}
+
+func runReserveMax(t *testing.T, lim *Limiter, req request, maxReserve time.Duration) *Reservation {
+       r := lim.reserveN(req.t, req.n, maxReserve)
+       if r.ok && (dSince(r.timeToAct) != dSince(req.act)) || r.ok != req.ok {
+               t.Errorf("lim.reserveN(t%d, %v, %v) = (t%d, %v) want (t%d, %v)",
+                       dSince(req.t), req.n, maxReserve, dSince(r.timeToAct), r.ok, dSince(req.act), req.ok)
+       }
+       return &r
+}
+
+func TestSimpleReserve(t *testing.T) {
+       lim := NewLimiter(10, 2)
+
+       runReserve(t, lim, request{t0, 2, t0, true})
+       runReserve(t, lim, request{t0, 2, t2, true})
+       runReserve(t, lim, request{t3, 2, t4, true})
+}
+
+func TestMix(t *testing.T) {
+       lim := NewLimiter(10, 2)
+
+       runReserve(t, lim, request{t0, 3, t1, false}) // should return false because n > Burst
+       runReserve(t, lim, request{t0, 2, t0, true})
+       run(t, lim, []allow{{t1, 2, false}}) // not enought tokens - don't allow
+       runReserve(t, lim, request{t1, 2, t2, true})
+       run(t, lim, []allow{{t1, 1, false}}) // negative tokens - don't allow
+       run(t, lim, []allow{{t3, 1, true}})
+}
+
+func TestCancelInvalid(t *testing.T) {
+       lim := NewLimiter(10, 2)
+
+       runReserve(t, lim, request{t0, 2, t0, true})
+       r := runReserve(t, lim, request{t0, 3, t3, false})
+       r.CancelAt(t0)                               // should have no effect
+       runReserve(t, lim, request{t0, 2, t2, true}) // did not get extra tokens
+}
+
+func TestCancelLast(t *testing.T) {
+       lim := NewLimiter(10, 2)
+
+       runReserve(t, lim, request{t0, 2, t0, true})
+       r := runReserve(t, lim, request{t0, 2, t2, true})
+       r.CancelAt(t1) // got 2 tokens back
+       runReserve(t, lim, request{t1, 2, t2, true})
+}
+
+func TestCancelTooLate(t *testing.T) {
+       lim := NewLimiter(10, 2)
+
+       runReserve(t, lim, request{t0, 2, t0, true})
+       r := runReserve(t, lim, request{t0, 2, t2, true})
+       r.CancelAt(t3) // too late to cancel - should have no effect
+       runReserve(t, lim, request{t3, 2, t4, true})
+}
+
+func TestCancel0Tokens(t *testing.T) {
+       lim := NewLimiter(10, 2)
+
+       runReserve(t, lim, request{t0, 2, t0, true})
+       r := runReserve(t, lim, request{t0, 1, t1, true})
+       runReserve(t, lim, request{t0, 1, t2, true})
+       r.CancelAt(t0) // got 0 tokens back
+       runReserve(t, lim, request{t0, 1, t3, true})
+}
+
+func TestCancel1Token(t *testing.T) {
+       lim := NewLimiter(10, 2)
+
+       runReserve(t, lim, request{t0, 2, t0, true})
+       r := runReserve(t, lim, request{t0, 2, t2, true})
+       runReserve(t, lim, request{t0, 1, t3, true})
+       r.CancelAt(t2) // got 1 token back
+       runReserve(t, lim, request{t2, 2, t4, true})
+}
+
+func TestCancelMulti(t *testing.T) {
+       lim := NewLimiter(10, 4)
+
+       runReserve(t, lim, request{t0, 4, t0, true})
+       rA := runReserve(t, lim, request{t0, 3, t3, true})
+       runReserve(t, lim, request{t0, 1, t4, true})
+       rC := runReserve(t, lim, request{t0, 1, t5, true})
+       rC.CancelAt(t1) // get 1 token back
+       rA.CancelAt(t1) // get 2 tokens back, as if C was never reserved
+       runReserve(t, lim, request{t1, 3, t5, true})
+}
+
+func TestReserveJumpBack(t *testing.T) {
+       lim := NewLimiter(10, 2)
+
+       runReserve(t, lim, request{t1, 2, t1, true}) // start at t1
+       runReserve(t, lim, request{t0, 1, t1, true}) // should violate Limit,Burst
+       runReserve(t, lim, request{t2, 2, t3, true})
+}
+
+func TestReserveJumpBackCancel(t *testing.T) {
+       lim := NewLimiter(10, 2)
+
+       runReserve(t, lim, request{t1, 2, t1, true}) // start at t1
+       r := runReserve(t, lim, request{t1, 2, t3, true})
+       runReserve(t, lim, request{t1, 1, t4, true})
+       r.CancelAt(t0)                               // cancel at t0, get 1 token back
+       runReserve(t, lim, request{t1, 2, t4, true}) // should violate Limit,Burst
+}
+
+func TestReserveSetLimit(t *testing.T) {
+       lim := NewLimiter(5, 2)
+
+       runReserve(t, lim, request{t0, 2, t0, true})
+       runReserve(t, lim, request{t0, 2, t4, true})
+       lim.SetLimitAt(t2, 10)
+       runReserve(t, lim, request{t2, 1, t4, true}) // violates Limit and Burst
+}
+
+func TestReserveSetBurst(t *testing.T) {
+       lim := NewLimiter(5, 2)
+
+       runReserve(t, lim, request{t0, 2, t0, true})
+       runReserve(t, lim, request{t0, 2, t4, true})
+       lim.SetBurstAt(t3, 4)
+       runReserve(t, lim, request{t0, 4, t9, true}) // violates Limit and Burst
+}
+
+func TestReserveSetLimitCancel(t *testing.T) {
+       lim := NewLimiter(5, 2)
+
+       runReserve(t, lim, request{t0, 2, t0, true})
+       r := runReserve(t, lim, request{t0, 2, t4, true})
+       lim.SetLimitAt(t2, 10)
+       r.CancelAt(t2) // 2 tokens back
+       runReserve(t, lim, request{t2, 2, t3, true})
+}
+
+func TestReserveMax(t *testing.T) {
+       lim := NewLimiter(10, 2)
+       maxT := d
+
+       runReserveMax(t, lim, request{t0, 2, t0, true}, maxT)
+       runReserveMax(t, lim, request{t0, 1, t1, true}, maxT)  // reserve for close future
+       runReserveMax(t, lim, request{t0, 1, t2, false}, maxT) // time to act too far in the future
+}
+
+type wait struct {
+       name   string
+       ctx    context.Context
+       n      int
+       delay  int // in multiples of d
+       nilErr bool
+}
+
+func runWait(t *testing.T, lim *Limiter, w wait) {
+       start := time.Now()
+       err := lim.WaitN(w.ctx, w.n)
+       delay := time.Now().Sub(start)
+       if (w.nilErr && err != nil) || (!w.nilErr && err == nil) || w.delay != dFromDuration(delay) {
+               errString := "<nil>"
+               if !w.nilErr {
+                       errString = "<non-nil error>"
+               }
+               t.Errorf("lim.WaitN(%v, lim, %v) = %v with delay %v ; want %v with delay %v",
+                       w.name, w.n, err, delay, errString, d*time.Duration(w.delay))
+       }
+}
+
+func TestWaitSimple(t *testing.T) {
+       lim := NewLimiter(10, 3)
+
+       ctx, cancel := context.WithCancel(context.Background())
+       cancel()
+       runWait(t, lim, wait{"already-cancelled", ctx, 1, 0, false})
+
+       runWait(t, lim, wait{"exceed-burst-error", context.Background(), 4, 0, false})
+
+       runWait(t, lim, wait{"act-now", context.Background(), 2, 0, true})
+       runWait(t, lim, wait{"act-later", context.Background(), 3, 2, true})
+}
+
+func TestWaitCancel(t *testing.T) {
+       lim := NewLimiter(10, 3)
+
+       ctx, cancel := context.WithCancel(context.Background())
+       runWait(t, lim, wait{"act-now", ctx, 2, 0, true}) // after this lim.tokens = 1
+       go func() {
+               time.Sleep(d)
+               cancel()
+       }()
+       runWait(t, lim, wait{"will-cancel", ctx, 3, 1, false})
+       // should get 3 tokens back, and have lim.tokens = 2
+       t.Logf("tokens:%v last:%v lastEvent:%v", lim.tokens, lim.last, lim.lastEvent)
+       runWait(t, lim, wait{"act-now-after-cancel", context.Background(), 2, 0, true})
+}
+
+func TestWaitTimeout(t *testing.T) {
+       lim := NewLimiter(10, 3)
+
+       ctx, cancel := context.WithTimeout(context.Background(), d)
+       defer cancel()
+       runWait(t, lim, wait{"act-now", ctx, 2, 0, true})
+       runWait(t, lim, wait{"w-timeout-err", ctx, 3, 0, false})
+}
+
+func TestWaitInf(t *testing.T) {
+       lim := NewLimiter(Inf, 0)
+
+       runWait(t, lim, wait{"exceed-burst-no-error", context.Background(), 3, 0, true})
+}
+
+func BenchmarkAllowN(b *testing.B) {
+       lim := NewLimiter(Every(1*time.Second), 1)
+       now := time.Now()
+       b.ReportAllocs()
+       b.ResetTimer()
+       b.RunParallel(func(pb *testing.PB) {
+               for pb.Next() {
+                       lim.AllowN(now, 1)
+               }
+       })
+}
+
+func BenchmarkWaitNNoDelay(b *testing.B) {
+       lim := NewLimiter(Limit(b.N), b.N)
+       ctx := context.Background()
+       b.ReportAllocs()
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               lim.WaitN(ctx, 1)
+       }
+}
index 89d6922..2eb16a6 100644 (file)
@@ -47,7 +47,7 @@ const (
 
 var (
        // The full version string
-       Version = "1.1.2"
+       Version = "1.1.9"
        // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse HEAD)"
        GitCommit string
        Status    *UpdateStatus
index 39d475f..116be49 100644 (file)
@@ -167,6 +167,8 @@ func (w *Wallet) BuildAnnotatedInput(tx *types.Tx, i uint32) *query.AnnotatedInp
                in.ControlProgram = orig.ControlProgram()
                in.Address = w.getAddressFromControlProgram(in.ControlProgram, true)
                in.SpentOutputID = e.MainchainOutputId
+               _, assetDefinition := w.getAliasDefinition(in.AssetID)
+               in.AssetDefinition = &assetDefinition
                arguments := orig.Arguments()
                for _, arg := range arguments {
                        in.WitnessArguments = append(in.WitnessArguments, arg)
index 278853a..0c9971a 100644 (file)
@@ -112,9 +112,6 @@ func (w *Wallet) filterAccountUtxo(utxos []*account.UTXO) []*account.UTXO {
        result := make([]*account.UTXO, 0, len(utxos))
        for s := range outsByScript {
                if !segwit.IsP2WScript([]byte(s)) {
-                       for _, utxo := range outsByScript[s] {
-                               result = append(result, utxo)
-                       }
                        continue
                }