mirror of
https://github.com/restic/restic.git
synced 2026-10-07 11:07:11 +00:00
Switch s3 library to allow for s3 compatible backends. Fixes #315
This commit is contained in:
1 parent
c969de7fad
commit
6d1552af51
68 files changed
+5994
-4655
No files matched your search
@@ -0,0 +1,2 @@
|
||||
*~
|
||||
*.test
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.5.1
|
||||
script:
|
||||
- go vet ./...
|
||||
- go test -race -v ./...
|
||||
notifications:
|
||||
slack:
|
||||
secure: HrOX2k6F/sEl6Rr4m5vHOdJCIwV42be0kz1Jy/WSMvrl/fQ8YkldKviLeWh4aWt1kclsYhNQ4FqGML+RIZYsdOqej4fAw9Vi5pZkI1MzPJq0UjrtMqkqzvD90eDGQYCKwaXjEIN8cohwJeb6X0B0HKAd9sqJW5GH5SwnhH5WWP8=
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
|
||||
### Developer Guidelines
|
||||
|
||||
``minio-go`` welcomes your contribution. To make the process as seamless as possible, we ask for the following:
|
||||
|
||||
* Go ahead and fork the project and make your changes. We encourage pull requests to discuss code changes.
|
||||
- Fork it
|
||||
- Create your feature branch (git checkout -b my-new-feature)
|
||||
- Commit your changes (git commit -am 'Add some feature')
|
||||
- Push to the branch (git push origin my-new-feature)
|
||||
- Create new Pull Request
|
||||
|
||||
* When you're ready to create a pull request, be sure to:
|
||||
- Have test cases for the new code. If you have questions about how to do it, please ask in your pull request.
|
||||
- Run `go fmt`
|
||||
- Squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request.
|
||||
- Make sure `go test -race ./...` and `go build` completes.
|
||||
|
||||
* Read [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project
|
||||
- `minio-go` project is strictly conformant with Golang style
|
||||
- if you happen to observe offending code, please feel free to send a pull request
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor 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, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
# Minio Go Library for Amazon S3 Compatible Cloud Storage [](https://gitter.im/minio/minio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
$ go get github.com/minio/minio-go
|
||||
```
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for bucket := range s3Client.ListBuckets() {
|
||||
if bucket.Err != nil {
|
||||
log.Fatalln(bucket.Err)
|
||||
}
|
||||
log.Println(bucket.Stat)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
### Bucket Level
|
||||
* [MakeBucket(bucket, acl) error](examples/s3/makebucket.go)
|
||||
* [BucketExists(bucket) error](examples/s3/bucketexists.go)
|
||||
* [RemoveBucket(bucket) error](examples/s3/removebucket.go)
|
||||
* [GetBucketACL(bucket) (BucketACL, error)](examples/s3/getbucketacl.go)
|
||||
* [SetBucketACL(bucket, BucketACL) error)](examples/s3/setbucketacl.go)
|
||||
* [ListBuckets() <-chan BucketStat](examples/s3/listbuckets.go)
|
||||
* [ListObjects(bucket, prefix, recursive) <-chan ObjectStat](examples/s3/listobjects.go)
|
||||
* [ListIncompleteUploads(bucket, prefix, recursive) <-chan ObjectMultipartStat](examples/s3/listincompleteuploads.go)
|
||||
|
||||
### Object Level
|
||||
* [PutObject(bucket, object, size, io.Reader) error](examples/s3/putobject.go)
|
||||
* [GetObject(bucket, object) (io.Reader, ObjectStat, error)](examples/s3/getobject.go)
|
||||
* [GetPartialObject(bucket, object, offset, length) (io.Reader, ObjectStat, error)](examples/s3/getpartialobject.go)
|
||||
* [StatObject(bucket, object) (ObjectStat, error)](examples/s3/statobject.go)
|
||||
* [RemoveObject(bucket, object) error](examples/s3/removeobject.go)
|
||||
* [RemoveIncompleteUpload(bucket, object) <-chan error](examples/s3/removeincompleteupload.go)
|
||||
|
||||
### Presigned Bucket/Object Level
|
||||
* [PresignedGetObject(bucket, object, time.Duration) (string, error)](examples/s3/presignedgetobject.go)
|
||||
* [PresignedPutObject(bucket, object, time.Duration) (string, error)](examples/s3/presignedputobject.go)
|
||||
* [PresignedPostPolicy(NewPostPolicy()) (map[string]string, error)](examples/s3/presignedpostpolicy.go)
|
||||
|
||||
### API Reference
|
||||
|
||||
[](http://godoc.org/github.com/minio/minio-go)
|
||||
|
||||
## Contribute
|
||||
|
||||
[Contributors Guide](./CONTRIBUTING.md)
|
||||
|
||||
[](https://travis-ci.org/minio/minio-go) [](https://ci.appveyor.com/project/harshavardhana/minio-go)
|
||||
+885
@@ -0,0 +1,885 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
separator = "/"
|
||||
)
|
||||
|
||||
// apiCore container to hold unexported internal functions
|
||||
type apiCore struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// closeResp close non nil response with any response Body
|
||||
func closeResp(resp *http.Response) {
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// putBucketRequest wrapper creates a new putBucket request
|
||||
func (a apiCore) putBucketRequest(bucket, acl, location string) (*request, error) {
|
||||
var r *request
|
||||
var err error
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "PUT",
|
||||
HTTPPath: separator + bucket,
|
||||
}
|
||||
var createBucketConfigBuffer *bytes.Reader
|
||||
// If location is set use it and create proper bucket configuration
|
||||
switch {
|
||||
case location != "":
|
||||
createBucketConfig := new(createBucketConfiguration)
|
||||
createBucketConfig.Location = location
|
||||
var createBucketConfigBytes []byte
|
||||
switch {
|
||||
case a.config.AcceptType == "application/xml":
|
||||
createBucketConfigBytes, err = xml.Marshal(createBucketConfig)
|
||||
case a.config.AcceptType == "application/json":
|
||||
createBucketConfigBytes, err = json.Marshal(createBucketConfig)
|
||||
default:
|
||||
createBucketConfigBytes, err = xml.Marshal(createBucketConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createBucketConfigBuffer = bytes.NewReader(createBucketConfigBytes)
|
||||
}
|
||||
switch {
|
||||
case createBucketConfigBuffer == nil:
|
||||
r, err = newRequest(op, a.config, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
r, err = newRequest(op, a.config, createBucketConfigBuffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.req.ContentLength = int64(createBucketConfigBuffer.Len())
|
||||
}
|
||||
// by default bucket is private
|
||||
switch {
|
||||
case acl != "":
|
||||
r.Set("x-amz-acl", acl)
|
||||
default:
|
||||
r.Set("x-amz-acl", "private")
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
/// Bucket Write Operations
|
||||
|
||||
// putBucket create a new bucket
|
||||
//
|
||||
// Requires valid AWS Access Key ID to authenticate requests
|
||||
// Anonymous requests are never allowed to create buckets
|
||||
//
|
||||
// optional arguments are acl and location - by default all buckets are created
|
||||
// with ``private`` acl and location set to US Standard if one wishes to set
|
||||
// different ACLs and Location one can set them properly.
|
||||
//
|
||||
// ACL valid values
|
||||
// ------------------
|
||||
// private - owner gets full access [DEFAULT]
|
||||
// public-read - owner gets full access, others get read access
|
||||
// public-read-write - owner gets full access, others get full access too
|
||||
// authenticated-read - owner gets full access, authenticated users get read access
|
||||
// ------------------
|
||||
//
|
||||
// Location valid values
|
||||
// ------------------
|
||||
// [ us-west-1 | us-west-2 | eu-west-1 | eu-central-1 | ap-southeast-1 | ap-northeast-1 | ap-southeast-2 | sa-east-1 ]
|
||||
//
|
||||
// Default - US standard
|
||||
func (a apiCore) putBucket(bucket, acl, location string) error {
|
||||
req, err := a.putBucketRequest(bucket, acl, location)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// putBucketRequestACL wrapper creates a new putBucketACL request
|
||||
func (a apiCore) putBucketACLRequest(bucket, acl string) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "PUT",
|
||||
HTTPPath: separator + bucket + "?acl",
|
||||
}
|
||||
req, err := newRequest(op, a.config, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Set("x-amz-acl", acl)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// putBucketACL set the permissions on an existing bucket using Canned ACL's
|
||||
func (a apiCore) putBucketACL(bucket, acl string) error {
|
||||
req, err := a.putBucketACLRequest(bucket, acl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getBucketACLRequest wrapper creates a new getBucketACL request
|
||||
func (a apiCore) getBucketACLRequest(bucket string) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "GET",
|
||||
HTTPPath: separator + bucket + "?acl",
|
||||
}
|
||||
req, err := newRequest(op, a.config, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// getBucketACL get the acl information on an existing bucket
|
||||
func (a apiCore) getBucketACL(bucket string) (accessControlPolicy, error) {
|
||||
req, err := a.getBucketACLRequest(bucket)
|
||||
if err != nil {
|
||||
return accessControlPolicy{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return accessControlPolicy{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return accessControlPolicy{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
policy := accessControlPolicy{}
|
||||
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &policy)
|
||||
if err != nil {
|
||||
return accessControlPolicy{}, err
|
||||
}
|
||||
if policy.AccessControlList.Grant == nil {
|
||||
errorResponse := ErrorResponse{
|
||||
Code: "InternalError",
|
||||
Message: "Access control Grant list is empty, please report this at https://github.com/minio/minio-go/issues",
|
||||
Resource: separator + bucket,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
return accessControlPolicy{}, errorResponse
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
// getBucketLocationRequest wrapper creates a new getBucketLocation request
|
||||
func (a apiCore) getBucketLocationRequest(bucket string) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "GET",
|
||||
HTTPPath: separator + bucket + "?location",
|
||||
}
|
||||
req, err := newRequest(op, a.config, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// getBucketLocation uses location subresource to return a bucket's region
|
||||
func (a apiCore) getBucketLocation(bucket string) (string, error) {
|
||||
req, err := a.getBucketLocationRequest(bucket)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
var locationConstraint string
|
||||
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &locationConstraint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return locationConstraint, nil
|
||||
}
|
||||
|
||||
// listObjectsRequest wrapper creates a new listObjects request
|
||||
func (a apiCore) listObjectsRequest(bucket, marker, prefix, delimiter string, maxkeys int) (*request, error) {
|
||||
// resourceQuery - get resources properly escaped and lined up before using them in http request
|
||||
resourceQuery := func() (*string, error) {
|
||||
switch {
|
||||
case marker != "":
|
||||
marker = fmt.Sprintf("&marker=%s", getURLEncodedPath(marker))
|
||||
fallthrough
|
||||
case prefix != "":
|
||||
prefix = fmt.Sprintf("&prefix=%s", getURLEncodedPath(prefix))
|
||||
fallthrough
|
||||
case delimiter != "":
|
||||
delimiter = fmt.Sprintf("&delimiter=%s", delimiter)
|
||||
}
|
||||
query := fmt.Sprintf("?max-keys=%d", maxkeys) + marker + prefix + delimiter
|
||||
return &query, nil
|
||||
}
|
||||
query, err := resourceQuery()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "GET",
|
||||
HTTPPath: separator + bucket + *query,
|
||||
}
|
||||
r, err := newRequest(op, a.config, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
/// Bucket Read Operations
|
||||
|
||||
// listObjects - (List Objects) - List some or all (up to 1000) of the objects in a bucket.
|
||||
//
|
||||
// You can use the request parameters as selection criteria to return a subset of the objects in a bucket.
|
||||
// request paramters :-
|
||||
// ---------
|
||||
// ?marker - Specifies the key to start with when listing objects in a bucket.
|
||||
// ?delimiter - A delimiter is a character you use to group keys.
|
||||
// ?prefix - Limits the response to keys that begin with the specified prefix.
|
||||
// ?max-keys - Sets the maximum number of keys returned in the response body.
|
||||
func (a apiCore) listObjects(bucket, marker, prefix, delimiter string, maxkeys int) (listBucketResult, error) {
|
||||
if err := invalidBucketError(bucket); err != nil {
|
||||
return listBucketResult{}, err
|
||||
}
|
||||
req, err := a.listObjectsRequest(bucket, marker, prefix, delimiter, maxkeys)
|
||||
if err != nil {
|
||||
return listBucketResult{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return listBucketResult{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return listBucketResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
listBucketResult := listBucketResult{}
|
||||
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &listBucketResult)
|
||||
if err != nil {
|
||||
return listBucketResult, err
|
||||
}
|
||||
// close body while returning, along with any error
|
||||
return listBucketResult, nil
|
||||
}
|
||||
|
||||
// headBucketRequest wrapper creates a new headBucket request
|
||||
func (a apiCore) headBucketRequest(bucket string) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "HEAD",
|
||||
HTTPPath: separator + bucket,
|
||||
}
|
||||
return newRequest(op, a.config, nil)
|
||||
}
|
||||
|
||||
// headBucket useful to determine if a bucket exists and you have permission to access it.
|
||||
func (a apiCore) headBucket(bucket string) error {
|
||||
if err := invalidBucketError(bucket); err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := a.headBucketRequest(bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// Head has no response body, handle it
|
||||
var errorResponse ErrorResponse
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "NoSuchBucket",
|
||||
Message: "The specified bucket does not exist.",
|
||||
Resource: separator + bucket,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
case http.StatusForbidden:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "AccessDenied",
|
||||
Message: "Access Denied",
|
||||
Resource: separator + bucket,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
default:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: resp.Status,
|
||||
Message: resp.Status,
|
||||
Resource: separator + bucket,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
}
|
||||
return errorResponse
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteBucketRequest wrapper creates a new deleteBucket request
|
||||
func (a apiCore) deleteBucketRequest(bucket string) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "DELETE",
|
||||
HTTPPath: separator + bucket,
|
||||
}
|
||||
return newRequest(op, a.config, nil)
|
||||
}
|
||||
|
||||
// deleteBucket deletes the bucket named in the URI
|
||||
//
|
||||
// NOTE: -
|
||||
// All objects (including all object versions and delete markers)
|
||||
// in the bucket must be deleted before successfully attempting this request
|
||||
func (a apiCore) deleteBucket(bucket string) error {
|
||||
if err := invalidBucketError(bucket); err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := a.deleteBucketRequest(bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
var errorResponse ErrorResponse
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "NoSuchBucket",
|
||||
Message: "The specified bucket does not exist.",
|
||||
Resource: separator + bucket,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
case http.StatusForbidden:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "AccessDenied",
|
||||
Message: "Access Denied",
|
||||
Resource: separator + bucket,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
case http.StatusConflict:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "Conflict",
|
||||
Message: "Bucket not empty",
|
||||
Resource: separator + bucket,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
default:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: resp.Status,
|
||||
Message: resp.Status,
|
||||
Resource: separator + bucket,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
}
|
||||
return errorResponse
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Object Read/Write/Stat Operations
|
||||
|
||||
func (a apiCore) putObjectUnAuthenticatedRequest(bucket, object, contentType string, size int64, body io.Reader) (*request, error) {
|
||||
if strings.TrimSpace(contentType) == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "PUT",
|
||||
HTTPPath: separator + bucket + separator + object,
|
||||
}
|
||||
r, err := newUnauthenticatedRequest(op, a.config, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Content-MD5 is not set consciously
|
||||
r.Set("Content-Type", contentType)
|
||||
r.req.ContentLength = size
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// putObjectUnAuthenticated - add an object to a bucket
|
||||
// NOTE: You must have WRITE permissions on a bucket to add an object to it.
|
||||
func (a apiCore) putObjectUnAuthenticated(bucket, object, contentType string, size int64, body io.Reader) (ObjectStat, error) {
|
||||
req, err := a.putObjectUnAuthenticatedRequest(bucket, object, contentType, size, body)
|
||||
if err != nil {
|
||||
return ObjectStat{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return ObjectStat{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return ObjectStat{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
var metadata ObjectStat
|
||||
metadata.ETag = strings.Trim(resp.Header.Get("ETag"), "\"") // trim off the odd double quotes
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
// putObjectRequest wrapper creates a new PutObject request
|
||||
func (a apiCore) putObjectRequest(bucket, object, contentType string, md5SumBytes []byte, size int64, body io.ReadSeeker) (*request, error) {
|
||||
if strings.TrimSpace(contentType) == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "PUT",
|
||||
HTTPPath: separator + bucket + separator + object,
|
||||
}
|
||||
r, err := newRequest(op, a.config, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set Content-MD5 as base64 encoded md5
|
||||
r.Set("Content-MD5", base64.StdEncoding.EncodeToString(md5SumBytes))
|
||||
r.Set("Content-Type", contentType)
|
||||
r.req.ContentLength = size
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// putObject - add an object to a bucket
|
||||
// NOTE: You must have WRITE permissions on a bucket to add an object to it.
|
||||
func (a apiCore) putObject(bucket, object, contentType string, md5SumBytes []byte, size int64, body io.ReadSeeker) (ObjectStat, error) {
|
||||
req, err := a.putObjectRequest(bucket, object, contentType, md5SumBytes, size, body)
|
||||
if err != nil {
|
||||
return ObjectStat{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return ObjectStat{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return ObjectStat{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
var metadata ObjectStat
|
||||
metadata.ETag = strings.Trim(resp.Header.Get("ETag"), "\"") // trim off the odd double quotes
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (a apiCore) presignedPostPolicy(p *PostPolicy) map[string]string {
|
||||
t := time.Now().UTC()
|
||||
r := new(request)
|
||||
r.config = a.config
|
||||
credential := getCredential(r.config.AccessKeyID, r.config.Region, t)
|
||||
p.addNewPolicy(policy{"eq", "$x-amz-date", t.Format(iso8601DateFormat)})
|
||||
p.addNewPolicy(policy{"eq", "$x-amz-algorithm", authHeader})
|
||||
p.addNewPolicy(policy{"eq", "$x-amz-credential", credential})
|
||||
|
||||
policyBase64 := p.base64()
|
||||
p.formData["policy"] = policyBase64
|
||||
p.formData["x-amz-algorithm"] = authHeader
|
||||
p.formData["x-amz-credential"] = credential
|
||||
p.formData["x-amz-date"] = t.Format(iso8601DateFormat)
|
||||
p.formData["x-amz-signature"] = r.PostPresignSignature(policyBase64, t)
|
||||
return p.formData
|
||||
}
|
||||
|
||||
func (a apiCore) presignedPutObject(bucket, object string, expires int64) (string, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "PUT",
|
||||
HTTPPath: separator + bucket + separator + object,
|
||||
}
|
||||
r, err := newPresignedRequest(op, a.config, strconv.FormatInt(expires, 10))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return r.PreSignV4()
|
||||
}
|
||||
|
||||
func (a apiCore) presignedGetObjectRequest(bucket, object string, expires, offset, length int64) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "GET",
|
||||
HTTPPath: separator + bucket + separator + object,
|
||||
}
|
||||
r, err := newPresignedRequest(op, a.config, strconv.FormatInt(expires, 10))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case length > 0 && offset > 0:
|
||||
r.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1))
|
||||
case offset > 0 && length == 0:
|
||||
r.Set("Range", fmt.Sprintf("bytes=%d-", offset))
|
||||
case length > 0 && offset == 0:
|
||||
r.Set("Range", fmt.Sprintf("bytes=-%d", length))
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (a apiCore) presignedGetObject(bucket, object string, expires, offset, length int64) (string, error) {
|
||||
if err := invalidArgumentError(object); err != nil {
|
||||
return "", err
|
||||
}
|
||||
req, err := a.presignedGetObjectRequest(bucket, object, expires, offset, length)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return req.PreSignV4()
|
||||
}
|
||||
|
||||
// getObjectRequest wrapper creates a new getObject request
|
||||
func (a apiCore) getObjectRequest(bucket, object string, offset, length int64) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "GET",
|
||||
HTTPPath: separator + bucket + separator + object,
|
||||
}
|
||||
r, err := newRequest(op, a.config, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case length > 0 && offset > 0:
|
||||
r.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1))
|
||||
case offset > 0 && length == 0:
|
||||
r.Set("Range", fmt.Sprintf("bytes=%d-", offset))
|
||||
case length > 0 && offset == 0:
|
||||
r.Set("Range", fmt.Sprintf("bytes=-%d", length))
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// getObject - retrieve object from Object Storage
|
||||
//
|
||||
// Additionally this function also takes range arguments to download the specified
|
||||
// range bytes of an object. Setting offset and length = 0 will download the full object.
|
||||
//
|
||||
// For more information about the HTTP Range header, go to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.
|
||||
func (a apiCore) getObject(bucket, object string, offset, length int64) (io.ReadCloser, ObjectStat, error) {
|
||||
if err := invalidArgumentError(object); err != nil {
|
||||
return nil, ObjectStat{}, err
|
||||
}
|
||||
req, err := a.getObjectRequest(bucket, object, offset, length)
|
||||
if err != nil {
|
||||
return nil, ObjectStat{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
if err != nil {
|
||||
return nil, ObjectStat{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
case http.StatusPartialContent:
|
||||
default:
|
||||
return nil, ObjectStat{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
md5sum := strings.Trim(resp.Header.Get("ETag"), "\"") // trim off the odd double quotes
|
||||
date, err := time.Parse(http.TimeFormat, resp.Header.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
return nil, ObjectStat{}, ErrorResponse{
|
||||
Code: "InternalError",
|
||||
Message: "Last-Modified time format not recognized, please report this issue at https://github.com/minio/minio-go/issues",
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
}
|
||||
contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
var objectstat ObjectStat
|
||||
objectstat.ETag = md5sum
|
||||
objectstat.Key = object
|
||||
objectstat.Size = resp.ContentLength
|
||||
objectstat.LastModified = date
|
||||
objectstat.ContentType = contentType
|
||||
|
||||
// do not close body here, caller will close
|
||||
return resp.Body, objectstat, nil
|
||||
}
|
||||
|
||||
// deleteObjectRequest wrapper creates a new deleteObject request
|
||||
func (a apiCore) deleteObjectRequest(bucket, object string) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "DELETE",
|
||||
HTTPPath: separator + bucket + separator + object,
|
||||
}
|
||||
return newRequest(op, a.config, nil)
|
||||
}
|
||||
|
||||
// deleteObject deletes a given object from a bucket
|
||||
func (a apiCore) deleteObject(bucket, object string) error {
|
||||
if err := invalidBucketError(bucket); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := invalidArgumentError(object); err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := a.deleteObjectRequest(bucket, object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
var errorResponse ErrorResponse
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "NoSuchKey",
|
||||
Message: "The specified key does not exist.",
|
||||
Resource: separator + bucket + separator + object,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
case http.StatusForbidden:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "AccessDenied",
|
||||
Message: "Access Denied",
|
||||
Resource: separator + bucket + separator + object,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
default:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: resp.Status,
|
||||
Message: resp.Status,
|
||||
Resource: separator + bucket + separator + object,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
}
|
||||
return errorResponse
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// headObjectRequest wrapper creates a new headObject request
|
||||
func (a apiCore) headObjectRequest(bucket, object string) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "HEAD",
|
||||
HTTPPath: separator + bucket + separator + object,
|
||||
}
|
||||
return newRequest(op, a.config, nil)
|
||||
}
|
||||
|
||||
// headObject retrieves metadata from an object without returning the object itself
|
||||
func (a apiCore) headObject(bucket, object string) (ObjectStat, error) {
|
||||
if err := invalidBucketError(bucket); err != nil {
|
||||
return ObjectStat{}, err
|
||||
}
|
||||
if err := invalidArgumentError(object); err != nil {
|
||||
return ObjectStat{}, err
|
||||
}
|
||||
req, err := a.headObjectRequest(bucket, object)
|
||||
if err != nil {
|
||||
return ObjectStat{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return ObjectStat{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
var errorResponse ErrorResponse
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "NoSuchKey",
|
||||
Message: "The specified key does not exist.",
|
||||
Resource: separator + bucket + separator + object,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
case http.StatusForbidden:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "AccessDenied",
|
||||
Message: "Access Denied",
|
||||
Resource: separator + bucket + separator + object,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
default:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: resp.Status,
|
||||
Message: resp.Status,
|
||||
Resource: separator + bucket + separator + object,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
|
||||
}
|
||||
return ObjectStat{}, errorResponse
|
||||
}
|
||||
}
|
||||
md5sum := strings.Trim(resp.Header.Get("ETag"), "\"") // trim off the odd double quotes
|
||||
size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
return ObjectStat{}, ErrorResponse{
|
||||
Code: "InternalError",
|
||||
Message: "Content-Length not recognized, please report this issue at https://github.com/minio/minio-go/issues",
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
}
|
||||
date, err := time.Parse(http.TimeFormat, resp.Header.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
return ObjectStat{}, ErrorResponse{
|
||||
Code: "InternalError",
|
||||
Message: "Last-Modified time format not recognized, please report this issue at https://github.com/minio/minio-go/issues",
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
}
|
||||
contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
var objectstat ObjectStat
|
||||
objectstat.ETag = md5sum
|
||||
objectstat.Key = object
|
||||
objectstat.Size = size
|
||||
objectstat.LastModified = date
|
||||
objectstat.ContentType = contentType
|
||||
return objectstat, nil
|
||||
}
|
||||
|
||||
/// Service Operations
|
||||
|
||||
// listBucketRequest wrapper creates a new listBuckets request
|
||||
func (a apiCore) listBucketsRequest() (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "GET",
|
||||
HTTPPath: separator,
|
||||
}
|
||||
return newRequest(op, a.config, nil)
|
||||
}
|
||||
|
||||
// listBuckets list of all buckets owned by the authenticated sender of the request
|
||||
func (a apiCore) listBuckets() (listAllMyBucketsResult, error) {
|
||||
req, err := a.listBucketsRequest()
|
||||
if err != nil {
|
||||
return listAllMyBucketsResult{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return listAllMyBucketsResult{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
// for un-authenticated requests, amazon sends a redirect handle it
|
||||
if resp.StatusCode == http.StatusTemporaryRedirect {
|
||||
return listAllMyBucketsResult{}, ErrorResponse{
|
||||
Code: "AccessDenied",
|
||||
Message: "Anonymous access is forbidden for this operation",
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
HostID: resp.Header.Get("x-amz-id-2"),
|
||||
}
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return listAllMyBucketsResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
listAllMyBucketsResult := listAllMyBucketsResult{}
|
||||
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &listAllMyBucketsResult)
|
||||
if err != nil {
|
||||
return listAllMyBucketsResult, err
|
||||
}
|
||||
return listAllMyBucketsResult, nil
|
||||
}
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// listMultipartUploadsRequest wrapper creates a new listMultipartUploads request
|
||||
func (a apiCore) listMultipartUploadsRequest(bucket, keymarker, uploadIDMarker, prefix, delimiter string, maxuploads int) (*request, error) {
|
||||
// resourceQuery - get resources properly escaped and lined up before using them in http request
|
||||
resourceQuery := func() (string, error) {
|
||||
switch {
|
||||
case keymarker != "":
|
||||
keymarker = fmt.Sprintf("&key-marker=%s", getURLEncodedPath(keymarker))
|
||||
fallthrough
|
||||
case uploadIDMarker != "":
|
||||
uploadIDMarker = fmt.Sprintf("&upload-id-marker=%s", uploadIDMarker)
|
||||
fallthrough
|
||||
case prefix != "":
|
||||
prefix = fmt.Sprintf("&prefix=%s", getURLEncodedPath(prefix))
|
||||
fallthrough
|
||||
case delimiter != "":
|
||||
delimiter = fmt.Sprintf("&delimiter=%s", delimiter)
|
||||
}
|
||||
query := fmt.Sprintf("?uploads&max-uploads=%d", maxuploads) + keymarker + uploadIDMarker + prefix + delimiter
|
||||
return query, nil
|
||||
}
|
||||
query, err := resourceQuery()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "GET",
|
||||
HTTPPath: separator + bucket + query,
|
||||
}
|
||||
r, err := newRequest(op, a.config, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// listMultipartUploads - (List Multipart Uploads) - Lists some or all (up to 1000) in-progress multipart uploads in a bucket.
|
||||
//
|
||||
// You can use the request parameters as selection criteria to return a subset of the uploads in a bucket.
|
||||
// request paramters :-
|
||||
// ---------
|
||||
// ?key-marker - Specifies the multipart upload after which listing should begin
|
||||
// ?upload-id-marker - Together with key-marker specifies the multipart upload after which listing should begin
|
||||
// ?delimiter - A delimiter is a character you use to group keys.
|
||||
// ?prefix - Limits the response to keys that begin with the specified prefix.
|
||||
// ?max-uploads - Sets the maximum number of multipart uploads returned in the response body.
|
||||
func (a apiCore) listMultipartUploads(bucket, keymarker, uploadIDMarker, prefix, delimiter string, maxuploads int) (listMultipartUploadsResult, error) {
|
||||
req, err := a.listMultipartUploadsRequest(bucket, keymarker, uploadIDMarker, prefix, delimiter, maxuploads)
|
||||
if err != nil {
|
||||
return listMultipartUploadsResult{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return listMultipartUploadsResult{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return listMultipartUploadsResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
listMultipartUploadsResult := listMultipartUploadsResult{}
|
||||
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &listMultipartUploadsResult)
|
||||
if err != nil {
|
||||
return listMultipartUploadsResult, err
|
||||
}
|
||||
// close body while returning, along with any error
|
||||
return listMultipartUploadsResult, nil
|
||||
}
|
||||
|
||||
// initiateMultipartRequest wrapper creates a new initiateMultiPart request
|
||||
func (a apiCore) initiateMultipartRequest(bucket, object string) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "POST",
|
||||
HTTPPath: separator + bucket + separator + object + "?uploads",
|
||||
}
|
||||
return newRequest(op, a.config, nil)
|
||||
}
|
||||
|
||||
// initiateMultipartUpload initiates a multipart upload and returns an upload ID
|
||||
func (a apiCore) initiateMultipartUpload(bucket, object string) (initiateMultipartUploadResult, error) {
|
||||
req, err := a.initiateMultipartRequest(bucket, object)
|
||||
if err != nil {
|
||||
return initiateMultipartUploadResult{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return initiateMultipartUploadResult{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return initiateMultipartUploadResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
initiateMultipartUploadResult := initiateMultipartUploadResult{}
|
||||
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &initiateMultipartUploadResult)
|
||||
if err != nil {
|
||||
return initiateMultipartUploadResult, err
|
||||
}
|
||||
return initiateMultipartUploadResult, nil
|
||||
}
|
||||
|
||||
// completeMultipartUploadRequest wrapper creates a new CompleteMultipartUpload request
|
||||
func (a apiCore) completeMultipartUploadRequest(bucket, object, uploadID string, complete completeMultipartUpload) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "POST",
|
||||
HTTPPath: separator + bucket + separator + object + "?uploadId=" + uploadID,
|
||||
}
|
||||
var completeMultipartUploadBytes []byte
|
||||
var err error
|
||||
switch {
|
||||
case a.config.AcceptType == "application/xml":
|
||||
completeMultipartUploadBytes, err = xml.Marshal(complete)
|
||||
case a.config.AcceptType == "application/json":
|
||||
completeMultipartUploadBytes, err = json.Marshal(complete)
|
||||
default:
|
||||
completeMultipartUploadBytes, err = xml.Marshal(complete)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
completeMultipartUploadBuffer := bytes.NewReader(completeMultipartUploadBytes)
|
||||
r, err := newRequest(op, a.config, completeMultipartUploadBuffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.req.ContentLength = int64(completeMultipartUploadBuffer.Len())
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// completeMultipartUpload completes a multipart upload by assembling previously uploaded parts.
|
||||
func (a apiCore) completeMultipartUpload(bucket, object, uploadID string, c completeMultipartUpload) (completeMultipartUploadResult, error) {
|
||||
req, err := a.completeMultipartUploadRequest(bucket, object, uploadID, c)
|
||||
if err != nil {
|
||||
return completeMultipartUploadResult{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return completeMultipartUploadResult{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return completeMultipartUploadResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
completeMultipartUploadResult := completeMultipartUploadResult{}
|
||||
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &completeMultipartUploadResult)
|
||||
if err != nil {
|
||||
return completeMultipartUploadResult, err
|
||||
}
|
||||
return completeMultipartUploadResult, nil
|
||||
}
|
||||
|
||||
// abortMultipartUploadRequest wrapper creates a new AbortMultipartUpload request
|
||||
func (a apiCore) abortMultipartUploadRequest(bucket, object, uploadID string) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "DELETE",
|
||||
HTTPPath: separator + bucket + separator + object + "?uploadId=" + uploadID,
|
||||
}
|
||||
return newRequest(op, a.config, nil)
|
||||
}
|
||||
|
||||
// abortMultipartUpload aborts a multipart upload for the given uploadID, all parts are deleted
|
||||
func (a apiCore) abortMultipartUpload(bucket, object, uploadID string) error {
|
||||
req, err := a.abortMultipartUploadRequest(bucket, object, uploadID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
// Abort has no response body, handle it
|
||||
var errorResponse ErrorResponse
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "NoSuchUpload",
|
||||
Message: "The specified multipart upload does not exist.",
|
||||
Resource: separator + bucket + separator + object,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
}
|
||||
case http.StatusForbidden:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: "AccessDenied",
|
||||
Message: "Access Denied",
|
||||
Resource: separator + bucket + separator + object,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
}
|
||||
default:
|
||||
errorResponse = ErrorResponse{
|
||||
Code: resp.Status,
|
||||
Message: "",
|
||||
Resource: separator + bucket + separator + object,
|
||||
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||
}
|
||||
}
|
||||
return errorResponse
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// listObjectPartsRequest wrapper creates a new ListObjectParts request
|
||||
func (a apiCore) listObjectPartsRequest(bucket, object, uploadID string, partNumberMarker, maxParts int) (*request, error) {
|
||||
// resourceQuery - get resources properly escaped and lined up before using them in http request
|
||||
resourceQuery := func() string {
|
||||
var partNumberMarkerStr string
|
||||
switch {
|
||||
case partNumberMarker != 0:
|
||||
partNumberMarkerStr = fmt.Sprintf("&part-number-marker=%d", partNumberMarker)
|
||||
}
|
||||
return fmt.Sprintf("?uploadId=%s&max-parts=%d", uploadID, maxParts) + partNumberMarkerStr
|
||||
}
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "GET",
|
||||
HTTPPath: separator + bucket + separator + object + resourceQuery(),
|
||||
}
|
||||
return newRequest(op, a.config, nil)
|
||||
}
|
||||
|
||||
// listObjectParts (List Parts) - lists some or all (up to 1000) parts that have been uploaded for a specific multipart upload
|
||||
//
|
||||
// You can use the request parameters as selection criteria to return a subset of the uploads in a bucket.
|
||||
// request paramters :-
|
||||
// ---------
|
||||
// ?part-number-marker - Specifies the part after which listing should begin.
|
||||
func (a apiCore) listObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (listObjectPartsResult, error) {
|
||||
req, err := a.listObjectPartsRequest(bucket, object, uploadID, partNumberMarker, maxParts)
|
||||
if err != nil {
|
||||
return listObjectPartsResult{}, err
|
||||
}
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return listObjectPartsResult{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return listObjectPartsResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
listObjectPartsResult := listObjectPartsResult{}
|
||||
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &listObjectPartsResult)
|
||||
if err != nil {
|
||||
return listObjectPartsResult, err
|
||||
}
|
||||
return listObjectPartsResult, nil
|
||||
}
|
||||
|
||||
// uploadPartRequest wrapper creates a new UploadPart request
|
||||
func (a apiCore) uploadPartRequest(bucket, object, uploadID string, md5SumBytes []byte, partNumber int, size int64, body io.ReadSeeker) (*request, error) {
|
||||
op := &operation{
|
||||
HTTPServer: a.config.Endpoint,
|
||||
HTTPMethod: "PUT",
|
||||
HTTPPath: separator + bucket + separator + object + "?partNumber=" + strconv.Itoa(partNumber) + "&uploadId=" + uploadID,
|
||||
}
|
||||
r, err := newRequest(op, a.config, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set Content-MD5 as base64 encoded md5
|
||||
r.Set("Content-MD5", base64.StdEncoding.EncodeToString(md5SumBytes))
|
||||
r.req.ContentLength = size
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// uploadPart uploads a part in a multipart upload.
|
||||
func (a apiCore) uploadPart(bucket, object, uploadID string, md5SumBytes []byte, partNumber int, size int64, body io.ReadSeeker) (completePart, error) {
|
||||
req, err := a.uploadPartRequest(bucket, object, uploadID, md5SumBytes, partNumber, size, body)
|
||||
if err != nil {
|
||||
return completePart{}, err
|
||||
}
|
||||
cPart := completePart{}
|
||||
cPart.PartNumber = partNumber
|
||||
cPart.ETag = "\"" + hex.EncodeToString(md5SumBytes) + "\""
|
||||
|
||||
// initiate the request
|
||||
resp, err := req.Do()
|
||||
defer closeResp(resp)
|
||||
if err != nil {
|
||||
return completePart{}, err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return completePart{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
||||
}
|
||||
}
|
||||
return cPart, nil
|
||||
}
|
||||
+1114
File diff suppressed because it is too large.
Load diff
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio_test
|
||||
|
||||
// bucketHandler is an http.Handler that verifies bucket responses and validates incoming requests
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type bucketHandler struct {
|
||||
resource string
|
||||
}
|
||||
|
||||
func (h bucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == "GET":
|
||||
switch {
|
||||
case r.URL.Path == "/":
|
||||
response := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><ListAllMyBucketsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Buckets><Bucket><Name>bucket</Name><CreationDate>2015-05-20T23:05:09.230Z</CreationDate></Bucket></Buckets><Owner><ID>minio</ID><DisplayName>minio</DisplayName></Owner></ListAllMyBucketsResult>")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
|
||||
w.Write(response)
|
||||
case r.URL.Path == "/bucket":
|
||||
_, ok := r.URL.Query()["acl"]
|
||||
if ok {
|
||||
response := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><AccessControlPolicy><Owner><ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID><DisplayName>CustomersName@amazon.com</DisplayName></Owner><AccessControlList><Grant><Grantee xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"CanonicalUser\"><ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID><DisplayName>CustomersName@amazon.com</DisplayName></Grantee><Permission>FULL_CONTROL</Permission></Grant></AccessControlList></AccessControlPolicy>")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
|
||||
w.Write(response)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case r.URL.Path == "/bucket":
|
||||
response := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Contents><ETag>\"259d04a13802ae09c7e41be50ccc6baa\"</ETag><Key>object</Key><LastModified>2015-05-21T18:24:21.097Z</LastModified><Size>22061</Size><Owner><ID>minio</ID><DisplayName>minio</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Delimiter></Delimiter><EncodingType></EncodingType><IsTruncated>false</IsTruncated><Marker></Marker><MaxKeys>1000</MaxKeys><Name>testbucket</Name><NextMarker></NextMarker><Prefix></Prefix></ListBucketResult>")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
|
||||
w.Write(response)
|
||||
}
|
||||
case r.Method == "PUT":
|
||||
switch {
|
||||
case r.URL.Path == h.resource:
|
||||
_, ok := r.URL.Query()["acl"]
|
||||
if ok {
|
||||
switch r.Header.Get("x-amz-acl") {
|
||||
case "public-read-write":
|
||||
fallthrough
|
||||
case "public-read":
|
||||
fallthrough
|
||||
case "private":
|
||||
fallthrough
|
||||
case "authenticated-read":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
default:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
case r.Method == "HEAD":
|
||||
switch {
|
||||
case r.URL.Path == h.resource:
|
||||
w.WriteHeader(http.StatusOK)
|
||||
default:
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
}
|
||||
case r.Method == "DELETE":
|
||||
switch {
|
||||
case r.URL.Path != h.resource:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
default:
|
||||
h.resource = ""
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// objectHandler is an http.Handler that verifies object responses and validates incoming requests
|
||||
type objectHandler struct {
|
||||
resource string
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (h objectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == "PUT":
|
||||
length, err := strconv.Atoi(r.Header.Get("Content-Length"))
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
_, err = io.CopyN(&buffer, r.Body, int64(length))
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(h.data, buffer.Bytes()) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("ETag", "\"9af2f8218b150c351ad802c6f3d66abe\"")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case r.Method == "HEAD":
|
||||
if r.URL.Path != h.resource {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(h.data)))
|
||||
w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
|
||||
w.Header().Set("ETag", "\"9af2f8218b150c351ad802c6f3d66abe\"")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case r.Method == "POST":
|
||||
_, ok := r.URL.Query()["uploads"]
|
||||
if ok {
|
||||
response := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><InitiateMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Bucket>example-bucket</Bucket><Key>object</Key><UploadId>XXBsb2FkIElEIGZvciBlbHZpbmcncyVcdS1tb3ZpZS5tMnRzEEEwbG9hZA</UploadId></InitiateMultipartUploadResult>")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
|
||||
w.Write(response)
|
||||
return
|
||||
}
|
||||
case r.Method == "GET":
|
||||
_, ok := r.URL.Query()["uploadId"]
|
||||
if ok {
|
||||
uploadID := r.URL.Query().Get("uploadId")
|
||||
if uploadID != "XXBsb2FkIElEIGZvciBlbHZpbmcncyVcdS1tb3ZpZS5tMnRzEEEwbG9hZA" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
response := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><ListPartsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Bucket>example-bucket</Bucket><Key>example-object</Key><UploadId>XXBsb2FkIElEIGZvciBlbHZpbmcncyVcdS1tb3ZpZS5tMnRzEEEwbG9hZA</UploadId><Initiator><ID>arn:aws:iam::111122223333:user/some-user-11116a31-17b5-4fb7-9df5-b288870f11xx</ID><DisplayName>umat-user-11116a31-17b5-4fb7-9df5-b288870f11xx</DisplayName></Initiator><Owner><ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID><DisplayName>someName</DisplayName></Owner><StorageClass>STANDARD</StorageClass><PartNumberMarker>1</PartNumberMarker><NextPartNumberMarker>3</NextPartNumberMarker><MaxParts>2</MaxParts><IsTruncated>true</IsTruncated><Part><PartNumber>2</PartNumber><LastModified>2010-11-10T20:48:34.000Z</LastModified><ETag>\"7778aef83f66abc1fa1e8477f296d394\"</ETag><Size>10485760</Size></Part><Part><PartNumber>3</PartNumber><LastModified>2010-11-10T20:48:33.000Z</LastModified><ETag>\"aaaa18db4cc2f85cedef654fccc4a4x8\"</ETag><Size>10485760</Size></Part></ListPartsResult>")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
|
||||
w.Write(response)
|
||||
return
|
||||
}
|
||||
if r.URL.Path != h.resource {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(h.data)))
|
||||
w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
|
||||
w.Header().Set("ETag", "\"9af2f8218b150c351ad802c6f3d66abe\"")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.Copy(w, bytes.NewReader(h.data))
|
||||
case r.Method == "DELETE":
|
||||
if r.URL.Path != h.resource {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
h.resource = ""
|
||||
h.data = nil
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestACLTypes(t *testing.T) {
|
||||
want := map[string]bool{
|
||||
"private": true,
|
||||
"public-read": true,
|
||||
"public-read-write": true,
|
||||
"authenticated-read": true,
|
||||
"invalid": false,
|
||||
}
|
||||
for acl, ok := range want {
|
||||
if BucketACL(acl).isValidBucketACL() != ok {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserAgent(t *testing.T) {
|
||||
conf := new(Config)
|
||||
conf.SetUserAgent("minio", "1.0", "amd64")
|
||||
if !strings.Contains(conf.userAgent, "minio") {
|
||||
t.Fatalf("Error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRegion(t *testing.T) {
|
||||
region := getRegion("s3.amazonaws.com")
|
||||
if region != "us-east-1" {
|
||||
t.Fatalf("Error")
|
||||
}
|
||||
region = getRegion("localhost:9000")
|
||||
if region != "milkyway" {
|
||||
t.Fatalf("Error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartSize(t *testing.T) {
|
||||
var maxPartSize int64 = 1024 * 1024 * 1024 * 5
|
||||
partSize := calculatePartSize(5000000000000000000)
|
||||
if partSize > minimumPartSize {
|
||||
if partSize > maxPartSize {
|
||||
t.Fatal("invalid result, cannot be bigger than maxPartSize 5GB")
|
||||
}
|
||||
}
|
||||
partSize = calculatePartSize(50000000000)
|
||||
if partSize > minimumPartSize {
|
||||
t.Fatal("invalid result, cannot be bigger than minimumPartSize 5MB")
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLEncoding(t *testing.T) {
|
||||
type urlStrings struct {
|
||||
name string
|
||||
encodedName string
|
||||
}
|
||||
|
||||
want := []urlStrings{
|
||||
{
|
||||
name: "bigfile-1._%",
|
||||
encodedName: "bigfile-1._%25",
|
||||
},
|
||||
{
|
||||
name: "本語",
|
||||
encodedName: "%E6%9C%AC%E8%AA%9E",
|
||||
},
|
||||
{
|
||||
name: "本語.1",
|
||||
encodedName: "%E6%9C%AC%E8%AA%9E.1",
|
||||
},
|
||||
{
|
||||
name: ">123>3123123",
|
||||
encodedName: "%3E123%3E3123123",
|
||||
},
|
||||
{
|
||||
name: "test 1 2.txt",
|
||||
encodedName: "test%201%202.txt",
|
||||
},
|
||||
{
|
||||
name: "test++ 1.txt",
|
||||
encodedName: "test%2B%2B%201.txt",
|
||||
},
|
||||
}
|
||||
|
||||
for _, u := range want {
|
||||
if u.encodedName != getURLEncodedPath(u.name) {
|
||||
t.Errorf("Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func TestBucketOperations(t *testing.T) {
|
||||
bucket := bucketHandler(bucketHandler{
|
||||
resource: "/bucket",
|
||||
})
|
||||
server := httptest.NewServer(bucket)
|
||||
defer server.Close()
|
||||
|
||||
a, err := minio.New(minio.Config{Endpoint: server.URL})
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
err = a.MakeBucket("bucket", "private")
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
err = a.BucketExists("bucket")
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
err = a.BucketExists("bucket1")
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if err.Error() != "Access Denied" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
err = a.SetBucketACL("bucket", "public-read-write")
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
acl, err := a.GetBucketACL("bucket")
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if acl != minio.BucketACL("private") {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
for b := range a.ListBuckets() {
|
||||
if b.Err != nil {
|
||||
t.Fatal(b.Err.Error())
|
||||
}
|
||||
if b.Stat.Name != "bucket" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
}
|
||||
|
||||
for o := range a.ListObjects("bucket", "", true) {
|
||||
if o.Err != nil {
|
||||
t.Fatal(o.Err.Error())
|
||||
}
|
||||
if o.Stat.Key != "object" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
}
|
||||
|
||||
err = a.RemoveBucket("bucket")
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
err = a.RemoveBucket("bucket1")
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if err.Error() != "The specified bucket does not exist." {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBucketOperationsFail(t *testing.T) {
|
||||
bucket := bucketHandler(bucketHandler{
|
||||
resource: "/bucket",
|
||||
})
|
||||
server := httptest.NewServer(bucket)
|
||||
defer server.Close()
|
||||
|
||||
a, err := minio.New(minio.Config{Endpoint: server.URL})
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
err = a.MakeBucket("bucket$$$", "private")
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
err = a.BucketExists("bucket.")
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
err = a.SetBucketACL("bucket-.", "public-read-write")
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
_, err = a.GetBucketACL("bucket??")
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
for o := range a.ListObjects("bucket??", "", true) {
|
||||
if o.Err == nil {
|
||||
t.Fatal(o.Err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
err = a.RemoveBucket("bucket??")
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
if err.Error() != "The specified bucket is not valid." {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectOperations(t *testing.T) {
|
||||
object := objectHandler(objectHandler{
|
||||
resource: "/bucket/object",
|
||||
data: []byte("Hello, World"),
|
||||
})
|
||||
server := httptest.NewServer(object)
|
||||
defer server.Close()
|
||||
|
||||
a, err := minio.New(minio.Config{Endpoint: server.URL})
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
data := []byte("Hello, World")
|
||||
err = a.PutObject("bucket", "object", "", int64(len(data)), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
metadata, err := a.StatObject("bucket", "object")
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if metadata.Key != "object" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if metadata.ETag != "9af2f8218b150c351ad802c6f3d66abe" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
reader, metadata, err := a.GetObject("bucket", "object")
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if metadata.Key != "object" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if metadata.ETag != "9af2f8218b150c351ad802c6f3d66abe" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
_, err = io.Copy(&buffer, reader)
|
||||
if !bytes.Equal(buffer.Bytes(), data) {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
err = a.RemoveObject("bucket", "object")
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
err = a.RemoveObject("bucket", "object1")
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if err.Error() != "The specified key does not exist." {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPresignedURL(t *testing.T) {
|
||||
object := objectHandler(objectHandler{
|
||||
resource: "/bucket/object",
|
||||
data: []byte("Hello, World"),
|
||||
})
|
||||
server := httptest.NewServer(object)
|
||||
defer server.Close()
|
||||
|
||||
a, err := minio.New(minio.Config{Endpoint: server.URL})
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
// should error out for invalid access keys
|
||||
_, err = a.PresignedGetObject("bucket", "object", time.Duration(1000)*time.Second)
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
|
||||
a, err = minio.New(minio.Config{
|
||||
Endpoint: server.URL,
|
||||
AccessKeyID: "accessKey",
|
||||
SecretAccessKey: "secretKey",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
url, err := a.PresignedGetObject("bucket", "object", time.Duration(1000)*time.Second)
|
||||
if err != nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if url == "" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
_, err = a.PresignedGetObject("bucket", "object", time.Duration(0)*time.Second)
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
_, err = a.PresignedGetObject("bucket", "object", time.Duration(604801)*time.Second)
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorResponse(t *testing.T) {
|
||||
errorResponse := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Error><Code>AccessDenied</Code><Message>Access Denied</Message><Resource>/mybucket/myphoto.jpg</Resource><RequestId>F19772218238A85A</RequestId><HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId></Error>")
|
||||
errorReader := bytes.NewReader(errorResponse)
|
||||
err := minio.BodyToErrorResponse(errorReader, "application/xml")
|
||||
if err == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if err.Error() != "Access Denied" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
resp := minio.ToErrorResponse(err)
|
||||
// valid all fields
|
||||
if resp == nil {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if resp.Code != "AccessDenied" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if resp.RequestID != "F19772218238A85A" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if resp.Message != "Access Denied" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if resp.Resource != "/mybucket/myphoto.jpg" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if resp.HostID != "GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if resp.ToXML() == "" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
if resp.ToJSON() == "" {
|
||||
t.Fatal("Error")
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# version format
|
||||
version: "{build}"
|
||||
|
||||
# Operating system (build VM template)
|
||||
os: Windows Server 2012 R2
|
||||
|
||||
clone_folder: c:\gopath\src\github.com\minio\minio-go
|
||||
|
||||
# environment variables
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
GO15VENDOREXPERIMENT: 1
|
||||
|
||||
# scripts that run after cloning repository
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
- rd C:\Go /s /q
|
||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.5.1.windows-amd64.zip
|
||||
- 7z x go1.5.1.windows-amd64.zip -oC:\ >nul
|
||||
- go version
|
||||
- go env
|
||||
- go get -u github.com/golang/lint/golint
|
||||
- go get -u golang.org/x/tools/cmd/vet
|
||||
- go get -u github.com/fzipp/gocyclo
|
||||
- go get -u github.com/remyoudompheng/go-misc/deadcode
|
||||
|
||||
# to run your custom scripts instead of automatic MSBuild
|
||||
build_script:
|
||||
- go vet ./...
|
||||
- gofmt -s -l .
|
||||
- golint github.com/minio/minio-go...
|
||||
- gocyclo -over 30 .
|
||||
- deadcode
|
||||
- go test
|
||||
- go test -race
|
||||
|
||||
# to disable automatic tests
|
||||
test: off
|
||||
|
||||
# to disable deployment
|
||||
deploy: off
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio
|
||||
|
||||
// BucketACL - bucket level access control
|
||||
type BucketACL string
|
||||
|
||||
// different types of ACL's currently supported for buckets
|
||||
const (
|
||||
bucketPrivate = BucketACL("private")
|
||||
bucketReadOnly = BucketACL("public-read")
|
||||
bucketPublic = BucketACL("public-read-write")
|
||||
bucketAuthenticated = BucketACL("authenticated-read")
|
||||
)
|
||||
|
||||
// String printer helper
|
||||
func (b BucketACL) String() string {
|
||||
if string(b) == "" {
|
||||
return "private"
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// isValidBucketACL - is provided acl string supported
|
||||
func (b BucketACL) isValidBucketACL() bool {
|
||||
switch true {
|
||||
case b.isPrivate():
|
||||
fallthrough
|
||||
case b.isReadOnly():
|
||||
fallthrough
|
||||
case b.isPublic():
|
||||
fallthrough
|
||||
case b.isAuthenticated():
|
||||
return true
|
||||
case b.String() == "private":
|
||||
// by default its "private"
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsPrivate - is acl Private
|
||||
func (b BucketACL) isPrivate() bool {
|
||||
return b == bucketPrivate
|
||||
}
|
||||
|
||||
// IsPublicRead - is acl PublicRead
|
||||
func (b BucketACL) isReadOnly() bool {
|
||||
return b == bucketReadOnly
|
||||
}
|
||||
|
||||
// IsPublicReadWrite - is acl PublicReadWrite
|
||||
func (b BucketACL) isPublic() bool {
|
||||
return b == bucketPublic
|
||||
}
|
||||
|
||||
// IsAuthenticated - is acl AuthenticatedRead
|
||||
func (b BucketACL) isAuthenticated() bool {
|
||||
return b == bucketAuthenticated
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"io"
|
||||
)
|
||||
|
||||
// part - message structure for results from the MultiPart
|
||||
type part struct {
|
||||
MD5Sum []byte
|
||||
ReadSeeker io.ReadSeeker
|
||||
Err error
|
||||
Len int64
|
||||
Num int // part number
|
||||
}
|
||||
|
||||
// skipPart - skipping uploaded parts
|
||||
type skipPart struct {
|
||||
md5sum []byte
|
||||
partNumber int
|
||||
}
|
||||
|
||||
// chopper reads from io.Reader, partitions the data into chunks of given chunksize, and sends
|
||||
// each chunk as io.ReadSeeker to the caller over a channel
|
||||
//
|
||||
// This method runs until an EOF or error occurs. If an error occurs,
|
||||
// the method sends the error over the channel and returns.
|
||||
// Before returning, the channel is always closed.
|
||||
//
|
||||
// additionally this function also skips list of parts if provided
|
||||
func chopper(reader io.Reader, chunkSize int64, skipParts []skipPart) <-chan part {
|
||||
ch := make(chan part, 3)
|
||||
go chopperInRoutine(reader, chunkSize, skipParts, ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
func chopperInRoutine(reader io.Reader, chunkSize int64, skipParts []skipPart, ch chan part) {
|
||||
defer close(ch)
|
||||
p := make([]byte, chunkSize)
|
||||
n, err := io.ReadFull(reader, p)
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF { // short read, only single part return
|
||||
m := md5.Sum(p[0:n])
|
||||
ch <- part{
|
||||
MD5Sum: m[:],
|
||||
ReadSeeker: bytes.NewReader(p[0:n]),
|
||||
Err: nil,
|
||||
Len: int64(n),
|
||||
Num: 1,
|
||||
}
|
||||
return
|
||||
}
|
||||
// catastrophic error send error and return
|
||||
if err != nil {
|
||||
ch <- part{
|
||||
ReadSeeker: nil,
|
||||
Err: err,
|
||||
Num: 0,
|
||||
}
|
||||
return
|
||||
}
|
||||
// send the first part
|
||||
var num = 1
|
||||
md5SumBytes := md5.Sum(p)
|
||||
sp := skipPart{
|
||||
partNumber: num,
|
||||
md5sum: md5SumBytes[:],
|
||||
}
|
||||
if !isPartNumberUploaded(sp, skipParts) {
|
||||
ch <- part{
|
||||
MD5Sum: md5SumBytes[:],
|
||||
ReadSeeker: bytes.NewReader(p),
|
||||
Err: nil,
|
||||
Len: int64(n),
|
||||
Num: num,
|
||||
}
|
||||
}
|
||||
for err == nil {
|
||||
var n int
|
||||
p := make([]byte, chunkSize)
|
||||
n, err = io.ReadFull(reader, p)
|
||||
if err != nil {
|
||||
if err != io.EOF && err != io.ErrUnexpectedEOF { // catastrophic error
|
||||
ch <- part{
|
||||
ReadSeeker: nil,
|
||||
Err: err,
|
||||
Num: 0,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
num++
|
||||
md5SumBytes := md5.Sum(p[0:n])
|
||||
sp := skipPart{
|
||||
partNumber: num,
|
||||
md5sum: md5SumBytes[:],
|
||||
}
|
||||
if isPartNumberUploaded(sp, skipParts) {
|
||||
continue
|
||||
}
|
||||
ch <- part{
|
||||
MD5Sum: md5SumBytes[:],
|
||||
ReadSeeker: bytes.NewReader(p[0:n]),
|
||||
Err: nil,
|
||||
Len: int64(n),
|
||||
Num: num,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// to verify if partNumber is part of the skip part list
|
||||
func isPartNumberUploaded(part skipPart, skipParts []skipPart) bool {
|
||||
for _, p := range skipParts {
|
||||
if p.partNumber == part.partNumber && bytes.Equal(p.md5sum, part.md5sum) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// decoder provides a unified decoding method interface
|
||||
type decoder interface {
|
||||
Decode(v interface{}) error
|
||||
}
|
||||
|
||||
// acceptTypeDecoder provide decoded value in given acceptType
|
||||
func acceptTypeDecoder(body io.Reader, acceptType string, v interface{}) error {
|
||||
var d decoder
|
||||
switch {
|
||||
case acceptType == "application/xml":
|
||||
d = xml.NewDecoder(body)
|
||||
case acceptType == "application/json":
|
||||
d = json.NewDecoder(body)
|
||||
default:
|
||||
d = xml.NewDecoder(body)
|
||||
}
|
||||
return d.Decode(v)
|
||||
}
|
||||
|
||||
// sum256Reader calculate sha256 sum for an input read seeker
|
||||
func sum256Reader(reader io.ReadSeeker) ([]byte, error) {
|
||||
h := sha256.New()
|
||||
var err error
|
||||
|
||||
start, _ := reader.Seek(0, 1)
|
||||
defer reader.Seek(start, 0)
|
||||
|
||||
for err == nil {
|
||||
length := 0
|
||||
byteBuffer := make([]byte, 1024*1024)
|
||||
length, err = reader.Read(byteBuffer)
|
||||
byteBuffer = byteBuffer[0:length]
|
||||
h.Write(byteBuffer)
|
||||
}
|
||||
|
||||
if err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// sum256 calculate sha256 sum for an input byte array
|
||||
func sum256(data []byte) []byte {
|
||||
hash := sha256.New()
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
// sumHMAC calculate hmac between two input byte array
|
||||
func sumHMAC(key []byte, data []byte) []byte {
|
||||
hash := hmac.New(sha256.New, key)
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
// getSigningKey hmac seed to calculate final signature
|
||||
func getSigningKey(secret, region string, t time.Time) []byte {
|
||||
date := sumHMAC([]byte("AWS4"+secret), []byte(t.Format(yyyymmdd)))
|
||||
regionbytes := sumHMAC(date, []byte(region))
|
||||
service := sumHMAC(regionbytes, []byte("s3"))
|
||||
signingKey := sumHMAC(service, []byte("aws4_request"))
|
||||
return signingKey
|
||||
}
|
||||
|
||||
// getSignature final signature in hexadecimal form
|
||||
func getSignature(signingKey []byte, stringToSign string) string {
|
||||
return hex.EncodeToString(sumHMAC(signingKey, []byte(stringToSign)))
|
||||
}
|
||||
|
||||
// getScope generate a string of a specific date, an AWS region, and a service
|
||||
func getScope(region string, t time.Time) string {
|
||||
scope := strings.Join([]string{
|
||||
t.Format(yyyymmdd),
|
||||
region,
|
||||
"s3",
|
||||
"aws4_request",
|
||||
}, "/")
|
||||
return scope
|
||||
}
|
||||
|
||||
// getCredential generate a credential string
|
||||
func getCredential(accessKeyID, region string, t time.Time) string {
|
||||
scope := getScope(region, t)
|
||||
return accessKeyID + "/" + scope
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"time"
|
||||
)
|
||||
|
||||
// listAllMyBucketsResult container for listBuckets response
|
||||
type listAllMyBucketsResult struct {
|
||||
// Container for one or more buckets.
|
||||
Buckets struct {
|
||||
Bucket []BucketStat
|
||||
}
|
||||
Owner owner
|
||||
}
|
||||
|
||||
// owner container for bucket owner information
|
||||
type owner struct {
|
||||
DisplayName string
|
||||
ID string
|
||||
}
|
||||
|
||||
// commonPrefix container for prefix response
|
||||
type commonPrefix struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// listBucketResult container for listObjects response
|
||||
type listBucketResult struct {
|
||||
CommonPrefixes []commonPrefix // A response can contain CommonPrefixes only if you have specified a delimiter
|
||||
Contents []ObjectStat // Metadata about each object returned
|
||||
Delimiter string
|
||||
|
||||
// Encoding type used to encode object keys in the response.
|
||||
EncodingType string
|
||||
|
||||
// A flag that indicates whether or not ListObjects returned all of the results
|
||||
// that satisfied the search criteria.
|
||||
IsTruncated bool
|
||||
Marker string
|
||||
MaxKeys int64
|
||||
Name string
|
||||
|
||||
// When response is truncated (the IsTruncated element value in the response
|
||||
// is true), you can use the key name in this field as marker in the subsequent
|
||||
// request to get next set of objects. Object storage lists objects in alphabetical
|
||||
// order Note: This element is returned only if you have delimiter request parameter
|
||||
// specified. If response does not include the NextMaker and it is truncated,
|
||||
// you can use the value of the last Key in the response as the marker in the
|
||||
// subsequent request to get the next set of object keys.
|
||||
NextMarker string
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// listMultipartUploadsResult container for ListMultipartUploads response
|
||||
type listMultipartUploadsResult struct {
|
||||
Bucket string
|
||||
KeyMarker string
|
||||
UploadIDMarker string `xml:"UploadIdMarker"`
|
||||
NextKeyMarker string
|
||||
NextUploadIDMarker string `xml:"NextUploadIdMarker"`
|
||||
EncodingType string
|
||||
MaxUploads int64
|
||||
IsTruncated bool
|
||||
Uploads []ObjectMultipartStat `xml:"Upload"`
|
||||
Prefix string
|
||||
Delimiter string
|
||||
CommonPrefixes []commonPrefix // A response can contain CommonPrefixes only if you specify a delimiter
|
||||
}
|
||||
|
||||
// initiator container for who initiated multipart upload
|
||||
type initiator struct {
|
||||
ID string
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
// partMetadata container for particular part of an object
|
||||
type partMetadata struct {
|
||||
// Part number identifies the part.
|
||||
PartNumber int
|
||||
|
||||
// Date and time the part was uploaded.
|
||||
LastModified time.Time
|
||||
|
||||
// Entity tag returned when the part was uploaded, usually md5sum of the part
|
||||
ETag string
|
||||
|
||||
// Size of the uploaded part data.
|
||||
Size int64
|
||||
}
|
||||
|
||||
// listObjectPartsResult container for ListObjectParts response.
|
||||
type listObjectPartsResult struct {
|
||||
Bucket string
|
||||
Key string
|
||||
UploadID string `xml:"UploadId"`
|
||||
|
||||
Initiator initiator
|
||||
Owner owner
|
||||
|
||||
StorageClass string
|
||||
PartNumberMarker int
|
||||
NextPartNumberMarker int
|
||||
MaxParts int
|
||||
|
||||
// Indicates whether the returned list of parts is truncated.
|
||||
IsTruncated bool
|
||||
Parts []partMetadata `xml:"Part"`
|
||||
|
||||
EncodingType string
|
||||
}
|
||||
|
||||
// initiateMultipartUploadResult container for InitiateMultiPartUpload response.
|
||||
type initiateMultipartUploadResult struct {
|
||||
Bucket string
|
||||
Key string
|
||||
UploadID string `xml:"UploadId"`
|
||||
}
|
||||
|
||||
// completeMultipartUploadResult container for completed multipart upload response.
|
||||
type completeMultipartUploadResult struct {
|
||||
Location string
|
||||
Bucket string
|
||||
Key string
|
||||
ETag string
|
||||
}
|
||||
|
||||
// completePart sub container lists individual part numbers and their md5sum, part of completeMultipartUpload.
|
||||
type completePart struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Part" json:"-"`
|
||||
|
||||
// Part number identifies the part.
|
||||
PartNumber int
|
||||
ETag string
|
||||
}
|
||||
|
||||
// completeMultipartUpload container for completing multipart upload
|
||||
type completeMultipartUpload struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUpload" json:"-"`
|
||||
Parts []completePart `xml:"Part"`
|
||||
}
|
||||
|
||||
// createBucketConfiguration container for bucket configuration
|
||||
type createBucketConfiguration struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CreateBucketConfiguration" json:"-"`
|
||||
Location string `xml:"LocationConstraint"`
|
||||
}
|
||||
|
||||
type grant struct {
|
||||
Grantee struct {
|
||||
ID string
|
||||
DisplayName string
|
||||
EmailAddress string
|
||||
Type string
|
||||
URI string
|
||||
}
|
||||
Permission string
|
||||
}
|
||||
|
||||
type accessControlPolicy struct {
|
||||
AccessControlList struct {
|
||||
Grant []grant
|
||||
}
|
||||
Owner owner
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
/* **** SAMPLE ERROR RESPONSE ****
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Error>
|
||||
<Code>AccessDenied</Code>
|
||||
<Message>Access Denied</Message>
|
||||
<Resource>/mybucket/myphoto.jpg</Resource>
|
||||
<RequestId>F19772218238A85A</RequestId>
|
||||
<HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId>
|
||||
</Error>
|
||||
*/
|
||||
|
||||
// ErrorResponse is the type error returned by some API operations.
|
||||
type ErrorResponse struct {
|
||||
XMLName xml.Name `xml:"Error" json:"-"`
|
||||
Code string
|
||||
Message string
|
||||
Resource string
|
||||
RequestID string `xml:"RequestId"`
|
||||
HostID string `xml:"HostId"`
|
||||
}
|
||||
|
||||
// ToErrorResponse returns parsed ErrorResponse struct, if input is nil or not ErrorResponse return value is nil
|
||||
// this fuction is useful when some one wants to dig deeper into the error structures over the network.
|
||||
//
|
||||
// for example:
|
||||
//
|
||||
// import s3 "github.com/minio/minio-go"
|
||||
// ...
|
||||
// ...
|
||||
// ..., err := s3.GetObject(...)
|
||||
// if err != nil {
|
||||
// resp := s3.ToErrorResponse(err)
|
||||
// fmt.Println(resp.ToXML())
|
||||
// }
|
||||
// ...
|
||||
// ...
|
||||
func ToErrorResponse(err error) *ErrorResponse {
|
||||
switch err := err.(type) {
|
||||
case ErrorResponse:
|
||||
return &err
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ToXML send raw xml marshalled as string
|
||||
func (e ErrorResponse) ToXML() string {
|
||||
b, err := xml.Marshal(&e)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// ToJSON send raw json marshalled as string
|
||||
func (e ErrorResponse) ToJSON() string {
|
||||
b, err := json.Marshal(&e)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Error formats HTTP error string
|
||||
func (e ErrorResponse) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// BodyToErrorResponse returns a new encoded ErrorResponse structure
|
||||
func BodyToErrorResponse(errBody io.Reader, acceptType string) error {
|
||||
var errorResponse ErrorResponse
|
||||
err := acceptTypeDecoder(errBody, acceptType, &errorResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errorResponse
|
||||
}
|
||||
|
||||
// invalidBucketToError - invalid bucket to errorResponse
|
||||
func invalidBucketError(bucket string) error {
|
||||
// verify bucket name in accordance with
|
||||
// - http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html
|
||||
isValidBucket := func(bucket string) bool {
|
||||
if len(bucket) < 3 || len(bucket) > 63 {
|
||||
return false
|
||||
}
|
||||
if bucket[0] == '.' || bucket[len(bucket)-1] == '.' {
|
||||
return false
|
||||
}
|
||||
if match, _ := regexp.MatchString("\\.\\.", bucket); match == true {
|
||||
return false
|
||||
}
|
||||
// We don't support buckets with '.' in them
|
||||
match, _ := regexp.MatchString("^[a-zA-Z][a-zA-Z0-9\\-]+[a-zA-Z0-9]$", bucket)
|
||||
return match
|
||||
}
|
||||
|
||||
if !isValidBucket(strings.TrimSpace(bucket)) {
|
||||
// no resource since bucket is empty string
|
||||
errorResponse := ErrorResponse{
|
||||
Code: "InvalidBucketName",
|
||||
Message: "The specified bucket is not valid.",
|
||||
RequestID: "minio",
|
||||
}
|
||||
return errorResponse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// invalidObjectError invalid object name to errorResponse
|
||||
func invalidObjectError(object string) error {
|
||||
if strings.TrimSpace(object) == "" || object == "" {
|
||||
// no resource since object name is empty
|
||||
errorResponse := ErrorResponse{
|
||||
Code: "NoSuchKey",
|
||||
Message: "The specified key does not exist.",
|
||||
RequestID: "minio",
|
||||
}
|
||||
return errorResponse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// invalidArgumentError invalid argument to errorResponse
|
||||
func invalidArgumentError(arg string) error {
|
||||
errorResponse := ErrorResponse{
|
||||
Code: "InvalidArgument",
|
||||
Message: "Invalid Argument",
|
||||
RequestID: "minio",
|
||||
}
|
||||
if strings.TrimSpace(arg) == "" || arg == "" {
|
||||
// no resource since arg is empty string
|
||||
return errorResponse
|
||||
}
|
||||
if !utf8.ValidString(arg) {
|
||||
// add resource to reply back with invalid string
|
||||
errorResponse.Resource = arg
|
||||
return errorResponse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = s3Client.BucketExists("mybucket")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
acl, err := s3Client.GetBucketACL("mybucket")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(acl)
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
reader, stat, err := s3Client.GetObject("mybucket", "myobject")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
localfile, err := os.Create("testfile")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer localfile.Close()
|
||||
|
||||
if _, err = io.CopyN(localfile, reader, stat.Size); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
reader, stat, err := s3Client.GetPartialObject("mybucket", "myobject", 0, 10)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
localfile, err := os.Create("testfile")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer localfile.Close()
|
||||
|
||||
if _, err = io.CopyN(localfile, reader, stat.Size); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for bucket := range s3Client.ListBuckets() {
|
||||
if bucket.Err != nil {
|
||||
log.Fatalln(bucket.Err)
|
||||
}
|
||||
log.Println(bucket.Stat)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
// Recursive
|
||||
for multipartObject := range s3Client.ListIncompleteUploads("mybucket", "myobject", true) {
|
||||
if multipartObject.Err != nil {
|
||||
log.Fatalln(multipartObject.Err)
|
||||
}
|
||||
log.Println(multipartObject)
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for object := range s3Client.ListObjects("mybucket", "", true) {
|
||||
if object.Err != nil {
|
||||
log.Fatalln(object.Err)
|
||||
}
|
||||
log.Println(object.Stat)
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = s3Client.MakeBucket("mybucket", "")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
object, err := os.Open("testfile")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer object.Close()
|
||||
objectInfo, err := object.Stat()
|
||||
if err != nil {
|
||||
object.Close()
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = s3Client.PutObject("mybucket", "myobject", "application/octet-stream", objectInfo.Size(), object)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = s3Client.RemoveBucket("mybucket")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
|
||||
}
|
||||
Generated
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for err := range s3Client.RemoveIncompleteUpload("mybucket", "myobject") {
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
log.Println("Success")
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = s3Client.RemoveObject("mybucket", "myobject")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = s3Client.SetBucketACL("mybucket", minio.BucketACL("public-read-write"))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
Endpoint: "https://play.minio.io:9000",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
stat, err := s3Client.StatObject("mybucket", "myobject")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(stat)
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = s3Client.BucketExists("mybucket")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
acl, err := s3Client.GetBucketACL("mybucket")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(acl)
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
reader, stat, err := s3Client.GetObject("mybucket", "myobject")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
localfile, err := os.Create("testfile")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer localfile.Close()
|
||||
|
||||
if _, err = io.CopyN(localfile, reader, stat.Size); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
reader, stat, err := s3Client.GetPartialObject("mybucket", "myobject", 0, 10)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
localfile, err := os.Create("testfile")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer localfile.Close()
|
||||
|
||||
if _, err = io.CopyN(localfile, reader, stat.Size); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for bucket := range s3Client.ListBuckets() {
|
||||
if bucket.Err != nil {
|
||||
log.Fatalln(bucket.Err)
|
||||
}
|
||||
log.Println(bucket.Stat)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
// Recursive
|
||||
for multipartObject := range s3Client.ListIncompleteUploads("mybucket", "myobject", true) {
|
||||
if multipartObject.Err != nil {
|
||||
log.Fatalln(multipartObject.Err)
|
||||
}
|
||||
log.Println(multipartObject)
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for object := range s3Client.ListObjects("mybucket", "", true) {
|
||||
if object.Err != nil {
|
||||
log.Fatalln(object.Err)
|
||||
}
|
||||
log.Println(object.Stat)
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = s3Client.MakeBucket("mybucket", "")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
}
|
||||
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
string, err := s3Client.PresignedGetObject("mybucket", "myobject", time.Duration(1000)*time.Second)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(string)
|
||||
}
|
||||
Generated
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
policy := minio.NewPostPolicy()
|
||||
policy.SetKey("myobject")
|
||||
policy.SetBucket("mybucket")
|
||||
policy.SetExpires(time.Now().UTC().AddDate(0, 0, 10)) // expires in 10 days
|
||||
m, err := s3Client.PresignedPostPolicy(policy)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("curl ")
|
||||
for k, v := range m {
|
||||
fmt.Printf("-F %s=%s ", k, v)
|
||||
}
|
||||
fmt.Printf("-F file=@/etc/bashrc ")
|
||||
fmt.Printf(config.Endpoint + "/mybucket\n")
|
||||
}
|
||||
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
string, err := s3Client.PresignedPutObject("mybucket", "myobject", time.Duration(1000)*time.Second)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(string)
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
object, err := os.Open("testfile")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer object.Close()
|
||||
objectInfo, err := object.Stat()
|
||||
if err != nil {
|
||||
object.Close()
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = s3Client.PutObject("mybucket", "myobject", "application/octet-stream", objectInfo.Size(), object)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = s3Client.RemoveBucket("mybucket")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
|
||||
}
|
||||
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for err := range s3Client.RemoveIncompleteUpload("mybucket", "myobject") {
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
log.Println("Success")
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = s3Client.RemoveObject("mybucket", "myobject")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = s3Client.SetBucketACL("mybucket", minio.BucketACL("public-read-write"))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := minio.Config{
|
||||
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
|
||||
SecretAccessKey: "YOUR-PASSWORD-HERE",
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
}
|
||||
s3Client, err := minio.New(config)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
stat, err := s3Client.StatObject("mybucket", "myobject")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(stat)
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
package minio
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// expirationDateFormat date format for expiration key in json policy
|
||||
const expirationDateFormat = "2006-01-02T15:04:05.999Z"
|
||||
|
||||
// Policy explanation: http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
|
||||
type policy struct {
|
||||
matchType string
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
// PostPolicy provides strict static type conversion and validation for Amazon S3's POST policy JSON string.
|
||||
type PostPolicy struct {
|
||||
expiration time.Time // expiration date and time of the POST policy.
|
||||
policies []policy
|
||||
contentLengthRange struct {
|
||||
min int
|
||||
max int
|
||||
}
|
||||
|
||||
// Post form data
|
||||
formData map[string]string
|
||||
}
|
||||
|
||||
// NewPostPolicy instantiate new post policy
|
||||
func NewPostPolicy() *PostPolicy {
|
||||
p := &PostPolicy{}
|
||||
p.policies = make([]policy, 0)
|
||||
p.formData = make(map[string]string)
|
||||
return p
|
||||
}
|
||||
|
||||
// SetExpires expiration time
|
||||
func (p *PostPolicy) SetExpires(t time.Time) error {
|
||||
if t.IsZero() {
|
||||
return errors.New("time input invalid")
|
||||
}
|
||||
p.expiration = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetKey Object name
|
||||
func (p *PostPolicy) SetKey(key string) error {
|
||||
if strings.TrimSpace(key) == "" || key == "" {
|
||||
return errors.New("key invalid")
|
||||
}
|
||||
policy := policy{"eq", "$key", key}
|
||||
p.policies = append(p.policies, policy)
|
||||
p.formData["key"] = key
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetKeyStartsWith Object name that can start with
|
||||
func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error {
|
||||
if strings.TrimSpace(keyStartsWith) == "" || keyStartsWith == "" {
|
||||
return errors.New("key-starts-with invalid")
|
||||
}
|
||||
policy := policy{"starts-with", "$key", keyStartsWith}
|
||||
p.policies = append(p.policies, policy)
|
||||
p.formData["key"] = keyStartsWith
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBucket bucket name
|
||||
func (p *PostPolicy) SetBucket(bucket string) error {
|
||||
if strings.TrimSpace(bucket) == "" || bucket == "" {
|
||||
return errors.New("bucket invalid")
|
||||
}
|
||||
policy := policy{"eq", "$bucket", bucket}
|
||||
p.policies = append(p.policies, policy)
|
||||
p.formData["bucket"] = bucket
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetContentType content-type
|
||||
func (p *PostPolicy) SetContentType(contentType string) error {
|
||||
if strings.TrimSpace(contentType) == "" || contentType == "" {
|
||||
return errors.New("contentType invalid")
|
||||
}
|
||||
policy := policy{"eq", "$Content-Type", contentType}
|
||||
if err := p.addNewPolicy(policy); err != nil {
|
||||
return err
|
||||
}
|
||||
p.formData["Content-Type"] = contentType
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetContentLength - set new min and max content legnth condition
|
||||
func (p *PostPolicy) SetContentLength(min, max int) error {
|
||||
if min > max {
|
||||
return errors.New("minimum cannot be bigger than maximum")
|
||||
}
|
||||
if min < 0 {
|
||||
return errors.New("minimum cannot be negative")
|
||||
}
|
||||
if max < 0 {
|
||||
return errors.New("maximum cannot be negative")
|
||||
}
|
||||
p.contentLengthRange.min = min
|
||||
p.contentLengthRange.max = max
|
||||
return nil
|
||||
}
|
||||
|
||||
// addNewPolicy - internal helper to validate adding new policies
|
||||
func (p *PostPolicy) addNewPolicy(po policy) error {
|
||||
if po.matchType == "" || po.key == "" || po.value == "" {
|
||||
return errors.New("policy invalid")
|
||||
}
|
||||
p.policies = append(p.policies, po)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stringer interface for printing in pretty manner
|
||||
func (p PostPolicy) String() string {
|
||||
return string(p.marshalJSON())
|
||||
}
|
||||
|
||||
// marshalJSON provides Marshalled JSON
|
||||
func (p PostPolicy) marshalJSON() []byte {
|
||||
expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
|
||||
var policiesStr string
|
||||
policies := []string{}
|
||||
for _, po := range p.policies {
|
||||
policies = append(policies, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.key, po.value))
|
||||
}
|
||||
if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
|
||||
policies = append(policies, fmt.Sprintf("[\"content-length-range\", %d, %d]",
|
||||
p.contentLengthRange.min, p.contentLengthRange.max))
|
||||
}
|
||||
if len(policies) > 0 {
|
||||
policiesStr = `"conditions":[` + strings.Join(policies, ",") + "]"
|
||||
}
|
||||
retStr := "{"
|
||||
retStr = retStr + expirationStr + ","
|
||||
retStr = retStr + policiesStr
|
||||
retStr = retStr + "}"
|
||||
return []byte(retStr)
|
||||
}
|
||||
|
||||
// base64 produces base64 of PostPolicy's Marshalled json
|
||||
func (p PostPolicy) base64() string {
|
||||
return base64.StdEncoding.EncodeToString(p.marshalJSON())
|
||||
}
|
||||
+498
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package minio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// operation - rest operation
|
||||
type operation struct {
|
||||
HTTPServer string
|
||||
HTTPMethod string
|
||||
HTTPPath string
|
||||
}
|
||||
|
||||
// request - a http request
|
||||
type request struct {
|
||||
req *http.Request
|
||||
config *Config
|
||||
body io.ReadSeeker
|
||||
expires string
|
||||
}
|
||||
|
||||
const (
|
||||
authHeader = "AWS4-HMAC-SHA256"
|
||||
iso8601DateFormat = "20060102T150405Z"
|
||||
yyyymmdd = "20060102"
|
||||
)
|
||||
|
||||
///
|
||||
/// Excerpts from @lsegal - https://github.com/aws/aws-sdk-js/issues/659#issuecomment-120477258
|
||||
///
|
||||
/// User-Agent:
|
||||
///
|
||||
/// This is ignored from signing because signing this causes problems with generating pre-signed URLs
|
||||
/// (that are executed by other agents) or when customers pass requests through proxies, which may
|
||||
/// modify the user-agent.
|
||||
///
|
||||
/// Content-Length:
|
||||
///
|
||||
/// This is ignored from signing because generating a pre-signed URL should not provide a content-length
|
||||
/// constraint, specifically when vending a S3 pre-signed PUT URL. The corollary to this is that when
|
||||
/// sending regular requests (non-pre-signed), the signature contains a checksum of the body, which
|
||||
/// implicitly validates the payload length (since changing the number of bytes would change the checksum)
|
||||
/// and therefore this header is not valuable in the signature.
|
||||
///
|
||||
/// Content-Type:
|
||||
///
|
||||
/// Signing this header causes quite a number of problems in browser environments, where browsers
|
||||
/// like to modify and normalize the content-type header in different ways. There is more information
|
||||
/// on this in https://github.com/aws/aws-sdk-js/issues/244. Avoiding this field simplifies logic
|
||||
/// and reduces the possibility of future bugs
|
||||
///
|
||||
/// Authorization:
|
||||
///
|
||||
/// Is skipped for obvious reasons
|
||||
///
|
||||
var ignoredHeaders = map[string]bool{
|
||||
"Authorization": true,
|
||||
"Content-Type": true,
|
||||
"Content-Length": true,
|
||||
"User-Agent": true,
|
||||
}
|
||||
|
||||
// getURLEncodedPath encode the strings from UTF-8 byte representations to HTML hex escape sequences
|
||||
//
|
||||
// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
|
||||
// non english characters cannot be parsed due to the nature in which url.Encode() is written
|
||||
//
|
||||
// This function on the other hand is a direct replacement for url.Encode() technique to support
|
||||
// pretty much every UTF-8 character.
|
||||
func getURLEncodedPath(pathName string) string {
|
||||
// if object matches reserved string, no need to encode them
|
||||
reservedNames := regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
|
||||
if reservedNames.MatchString(pathName) {
|
||||
return pathName
|
||||
}
|
||||
var encodedPathname string
|
||||
for _, s := range pathName {
|
||||
if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
|
||||
encodedPathname = encodedPathname + string(s)
|
||||
continue
|
||||
}
|
||||
switch s {
|
||||
case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
|
||||
encodedPathname = encodedPathname + string(s)
|
||||
continue
|
||||
default:
|
||||
len := utf8.RuneLen(s)
|
||||
if len < 0 {
|
||||
// if utf8 cannot convert return the same string as is
|
||||
return pathName
|
||||
}
|
||||
u := make([]byte, len)
|
||||
utf8.EncodeRune(u, s)
|
||||
for _, r := range u {
|
||||
hex := hex.EncodeToString([]byte{r})
|
||||
encodedPathname = encodedPathname + "%" + strings.ToUpper(hex)
|
||||
}
|
||||
}
|
||||
}
|
||||
return encodedPathname
|
||||
}
|
||||
|
||||
func path2BucketAndObject(path string) (bucketName, objectName string) {
|
||||
pathSplits := strings.SplitN(path, "?", 2)
|
||||
splits := strings.SplitN(pathSplits[0], separator, 3)
|
||||
switch len(splits) {
|
||||
case 0, 1:
|
||||
bucketName = ""
|
||||
objectName = ""
|
||||
case 2:
|
||||
bucketName = splits[1]
|
||||
objectName = ""
|
||||
case 3:
|
||||
bucketName = splits[1]
|
||||
objectName = splits[2]
|
||||
}
|
||||
return bucketName, objectName
|
||||
}
|
||||
|
||||
// path2Object gives objectName from URL path
|
||||
func path2Object(path string) (objectName string) {
|
||||
_, objectName = path2BucketAndObject(path)
|
||||
return
|
||||
}
|
||||
|
||||
// path2Bucket gives bucketName from URL path
|
||||
func path2Bucket(path string) (bucketName string) {
|
||||
bucketName, _ = path2BucketAndObject(path)
|
||||
return
|
||||
}
|
||||
|
||||
// path2Query gives query part from URL path
|
||||
func path2Query(path string) (query string) {
|
||||
pathSplits := strings.SplitN(path, "?", 2)
|
||||
if len(pathSplits) > 1 {
|
||||
query = pathSplits[1]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (op *operation) getRequestURL(config Config) (url string) {
|
||||
// parse URL for the combination of HTTPServer + HTTPPath
|
||||
url = op.HTTPServer + separator
|
||||
if !config.isVirtualStyle {
|
||||
url += path2Bucket(op.HTTPPath)
|
||||
}
|
||||
objectName := getURLEncodedPath(path2Object(op.HTTPPath))
|
||||
queryPath := path2Query(op.HTTPPath)
|
||||
if objectName == "" && queryPath != "" {
|
||||
url += "?" + queryPath
|
||||
return
|
||||
}
|
||||
if objectName != "" && queryPath == "" {
|
||||
if strings.HasSuffix(url, separator) {
|
||||
url += objectName
|
||||
} else {
|
||||
url += separator + objectName
|
||||
}
|
||||
return
|
||||
}
|
||||
if objectName != "" && queryPath != "" {
|
||||
if strings.HasSuffix(url, separator) {
|
||||
url += objectName + "?" + queryPath
|
||||
} else {
|
||||
url += separator + objectName + "?" + queryPath
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newPresignedRequest(op *operation, config *Config, expires string) (*request, error) {
|
||||
// if no method default to POST
|
||||
method := op.HTTPMethod
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
u := op.getRequestURL(*config)
|
||||
|
||||
// get a new HTTP request, for the requested method
|
||||
req, err := http.NewRequest(method, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set UserAgent
|
||||
req.Header.Set("User-Agent", config.userAgent)
|
||||
|
||||
// set Accept header for response encoding style, if available
|
||||
if config.AcceptType != "" {
|
||||
req.Header.Set("Accept", config.AcceptType)
|
||||
}
|
||||
|
||||
// save for subsequent use
|
||||
r := new(request)
|
||||
r.config = config
|
||||
r.expires = expires
|
||||
r.req = req
|
||||
r.body = nil
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// newUnauthenticatedRequest - instantiate a new unauthenticated request
|
||||
func newUnauthenticatedRequest(op *operation, config *Config, body io.Reader) (*request, error) {
|
||||
// if no method default to POST
|
||||
method := op.HTTPMethod
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
u := op.getRequestURL(*config)
|
||||
|
||||
// get a new HTTP request, for the requested method
|
||||
req, err := http.NewRequest(method, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set UserAgent
|
||||
req.Header.Set("User-Agent", config.userAgent)
|
||||
|
||||
// set Accept header for response encoding style, if available
|
||||
if config.AcceptType != "" {
|
||||
req.Header.Set("Accept", config.AcceptType)
|
||||
}
|
||||
|
||||
// add body
|
||||
switch {
|
||||
case body == nil:
|
||||
req.Body = nil
|
||||
default:
|
||||
req.Body = ioutil.NopCloser(body)
|
||||
}
|
||||
|
||||
// save for subsequent use
|
||||
r := new(request)
|
||||
r.req = req
|
||||
r.config = config
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// newRequest - instantiate a new request
|
||||
func newRequest(op *operation, config *Config, body io.ReadSeeker) (*request, error) {
|
||||
// if no method default to POST
|
||||
method := op.HTTPMethod
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
u := op.getRequestURL(*config)
|
||||
|
||||
// get a new HTTP request, for the requested method
|
||||
req, err := http.NewRequest(method, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set UserAgent
|
||||
req.Header.Set("User-Agent", config.userAgent)
|
||||
|
||||
// set Accept header for response encoding style, if available
|
||||
if config.AcceptType != "" {
|
||||
req.Header.Set("Accept", config.AcceptType)
|
||||
}
|
||||
|
||||
// add body
|
||||
switch {
|
||||
case body == nil:
|
||||
req.Body = nil
|
||||
default:
|
||||
req.Body = ioutil.NopCloser(body)
|
||||
}
|
||||
|
||||
// save for subsequent use
|
||||
r := new(request)
|
||||
r.config = config
|
||||
r.req = req
|
||||
r.body = body
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Do - start the request
|
||||
func (r *request) Do() (resp *http.Response, err error) {
|
||||
if r.config.AccessKeyID != "" && r.config.SecretAccessKey != "" {
|
||||
r.SignV4()
|
||||
}
|
||||
transport := http.DefaultTransport
|
||||
if r.config.Transport != nil {
|
||||
transport = r.config.Transport
|
||||
}
|
||||
// do not use http.Client{}, while it may seem intuitive but the problem seems to be
|
||||
// that http.Client{} internally follows redirects and there is no easier way to disable
|
||||
// it from outside using a configuration parameter -
|
||||
// this auto redirect causes complications in verifying subsequent errors
|
||||
//
|
||||
// The best is to use RoundTrip() directly, so the request comes back to the caller where
|
||||
// we are going to handle such replies. And indeed that is the right thing to do here.
|
||||
//
|
||||
return transport.RoundTrip(r.req)
|
||||
}
|
||||
|
||||
// Set - set additional headers if any
|
||||
func (r *request) Set(key, value string) {
|
||||
r.req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
// Get - get header values
|
||||
func (r *request) Get(key string) string {
|
||||
return r.req.Header.Get(key)
|
||||
}
|
||||
|
||||
// getHashedPayload get the hexadecimal value of the SHA256 hash of the request payload
|
||||
func (r *request) getHashedPayload() string {
|
||||
hash := func() string {
|
||||
switch {
|
||||
case r.expires != "":
|
||||
return "UNSIGNED-PAYLOAD"
|
||||
case r.body == nil:
|
||||
return hex.EncodeToString(sum256([]byte{}))
|
||||
default:
|
||||
sum256Bytes, _ := sum256Reader(r.body)
|
||||
return hex.EncodeToString(sum256Bytes)
|
||||
}
|
||||
}
|
||||
hashedPayload := hash()
|
||||
if hashedPayload != "UNSIGNED-PAYLOAD" {
|
||||
r.req.Header.Set("X-Amz-Content-Sha256", hashedPayload)
|
||||
}
|
||||
return hashedPayload
|
||||
}
|
||||
|
||||
// getCanonicalHeaders generate a list of request headers with their values
|
||||
func (r *request) getCanonicalHeaders() string {
|
||||
var headers []string
|
||||
vals := make(map[string][]string)
|
||||
for k, vv := range r.req.Header {
|
||||
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
|
||||
continue // ignored header
|
||||
}
|
||||
headers = append(headers, strings.ToLower(k))
|
||||
vals[strings.ToLower(k)] = vv
|
||||
}
|
||||
headers = append(headers, "host")
|
||||
sort.Strings(headers)
|
||||
|
||||
var buf bytes.Buffer
|
||||
for _, k := range headers {
|
||||
buf.WriteString(k)
|
||||
buf.WriteByte(':')
|
||||
switch {
|
||||
case k == "host":
|
||||
buf.WriteString(r.req.URL.Host)
|
||||
fallthrough
|
||||
default:
|
||||
for idx, v := range vals[k] {
|
||||
if idx > 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
buf.WriteString(v)
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// getSignedHeaders generate a string i.e alphabetically sorted, semicolon-separated list of lowercase request header names
|
||||
func (r *request) getSignedHeaders() string {
|
||||
var headers []string
|
||||
for k := range r.req.Header {
|
||||
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
|
||||
continue // ignored header
|
||||
}
|
||||
headers = append(headers, strings.ToLower(k))
|
||||
}
|
||||
headers = append(headers, "host")
|
||||
sort.Strings(headers)
|
||||
return strings.Join(headers, ";")
|
||||
}
|
||||
|
||||
// getCanonicalRequest generate a canonical request of style
|
||||
//
|
||||
// canonicalRequest =
|
||||
// <HTTPMethod>\n
|
||||
// <CanonicalURI>\n
|
||||
// <CanonicalQueryString>\n
|
||||
// <CanonicalHeaders>\n
|
||||
// <SignedHeaders>\n
|
||||
// <HashedPayload>
|
||||
//
|
||||
func (r *request) getCanonicalRequest(hashedPayload string) string {
|
||||
r.req.URL.RawQuery = strings.Replace(r.req.URL.Query().Encode(), "+", "%20", -1)
|
||||
canonicalRequest := strings.Join([]string{
|
||||
r.req.Method,
|
||||
getURLEncodedPath(r.req.URL.Path),
|
||||
r.req.URL.RawQuery,
|
||||
r.getCanonicalHeaders(),
|
||||
r.getSignedHeaders(),
|
||||
hashedPayload,
|
||||
}, "\n")
|
||||
return canonicalRequest
|
||||
}
|
||||
|
||||
// getStringToSign a string based on selected query values
|
||||
func (r *request) getStringToSign(canonicalRequest string, t time.Time) string {
|
||||
stringToSign := authHeader + "\n" + t.Format(iso8601DateFormat) + "\n"
|
||||
stringToSign = stringToSign + getScope(r.config.Region, t) + "\n"
|
||||
stringToSign = stringToSign + hex.EncodeToString(sum256([]byte(canonicalRequest)))
|
||||
return stringToSign
|
||||
}
|
||||
|
||||
// Presign the request, in accordance with - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
|
||||
func (r *request) PreSignV4() (string, error) {
|
||||
if r.config.AccessKeyID == "" && r.config.SecretAccessKey == "" {
|
||||
return "", errors.New("presign requires accesskey and secretkey")
|
||||
}
|
||||
r.SignV4()
|
||||
return r.req.URL.String(), nil
|
||||
}
|
||||
|
||||
func (r *request) PostPresignSignature(policyBase64 string, t time.Time) string {
|
||||
signingkey := getSigningKey(r.config.SecretAccessKey, r.config.Region, t)
|
||||
signature := getSignature(signingkey, policyBase64)
|
||||
return signature
|
||||
}
|
||||
|
||||
// SignV4 the request before Do(), in accordance with - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
|
||||
func (r *request) SignV4() {
|
||||
query := r.req.URL.Query()
|
||||
if r.expires != "" {
|
||||
query.Set("X-Amz-Algorithm", authHeader)
|
||||
}
|
||||
t := time.Now().UTC()
|
||||
// Add date if not present
|
||||
if r.expires != "" {
|
||||
query.Set("X-Amz-Date", t.Format(iso8601DateFormat))
|
||||
query.Set("X-Amz-Expires", r.expires)
|
||||
} else {
|
||||
r.Set("X-Amz-Date", t.Format(iso8601DateFormat))
|
||||
}
|
||||
|
||||
hashedPayload := r.getHashedPayload()
|
||||
signedHeaders := r.getSignedHeaders()
|
||||
if r.expires != "" {
|
||||
query.Set("X-Amz-SignedHeaders", signedHeaders)
|
||||
}
|
||||
credential := getCredential(r.config.AccessKeyID, r.config.Region, t)
|
||||
if r.expires != "" {
|
||||
query.Set("X-Amz-Credential", credential)
|
||||
r.req.URL.RawQuery = query.Encode()
|
||||
}
|
||||
canonicalRequest := r.getCanonicalRequest(hashedPayload)
|
||||
stringToSign := r.getStringToSign(canonicalRequest, t)
|
||||
signingKey := getSigningKey(r.config.SecretAccessKey, r.config.Region, t)
|
||||
signature := getSignature(signingKey, stringToSign)
|
||||
|
||||
if r.expires != "" {
|
||||
r.req.URL.RawQuery += "&X-Amz-Signature=" + signature
|
||||
} else {
|
||||
// final Authorization header
|
||||
parts := []string{
|
||||
authHeader + " Credential=" + credential,
|
||||
"SignedHeaders=" + signedHeaders,
|
||||
"Signature=" + signature,
|
||||
}
|
||||
auth := strings.Join(parts, ", ")
|
||||
r.Set("Authorization", auth)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user