mirror of
https://github.com/minio/minio.git
synced 2025-11-10 05:59:43 -05:00
Move all server and controller packages into top-level
This commit is contained in:
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Minio 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 controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
router "github.com/gorilla/mux"
|
||||
jsonrpc "github.com/gorilla/rpc/v2"
|
||||
"github.com/gorilla/rpc/v2/json"
|
||||
"github.com/minio/minio/pkg/controller/rpc"
|
||||
)
|
||||
|
||||
// getRPCHandler rpc handler
|
||||
func getRPCHandler() http.Handler {
|
||||
s := jsonrpc.NewServer()
|
||||
s.RegisterCodec(json.NewCodec(), "application/json")
|
||||
s.RegisterService(new(rpc.VersionService), "Version")
|
||||
s.RegisterService(new(rpc.DonutService), "Donut")
|
||||
s.RegisterService(new(rpc.AuthService), "Auth")
|
||||
s.RegisterService(new(rpc.ServerService), "Server")
|
||||
// Add new RPC services here
|
||||
return registerRPC(router.NewRouter(), s)
|
||||
}
|
||||
|
||||
// registerRPC - register rpc handlers
|
||||
func registerRPC(mux *router.Router, s *jsonrpc.Server) http.Handler {
|
||||
mux.Handle("/rpc", s)
|
||||
return mux
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Minio 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 rpc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
// AuthService auth service
|
||||
type AuthService struct{}
|
||||
|
||||
// AuthArgs auth params
|
||||
type AuthArgs struct {
|
||||
User string `json:"user"`
|
||||
}
|
||||
|
||||
// AuthReply reply with new access keys and secret ids
|
||||
type AuthReply struct {
|
||||
Name string `json:"name"`
|
||||
AccessKeyID string `json:"accessKeyId"`
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
}
|
||||
|
||||
// generateAuth generate new auth keys for a user
|
||||
func generateAuth(args *AuthArgs, reply *AuthReply) *probe.Error {
|
||||
config, err := auth.LoadConfig()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err.ToGoError()) {
|
||||
// Initialize new config, since config file doesn't exist yet
|
||||
config = &auth.Config{}
|
||||
config.Version = "0.0.1"
|
||||
config.Users = make(map[string]*auth.User)
|
||||
} else {
|
||||
return err.Trace()
|
||||
}
|
||||
}
|
||||
if _, ok := config.Users[args.User]; ok {
|
||||
return probe.NewError(errors.New("Credentials already set, if you wish to change this invoke Reset() method"))
|
||||
}
|
||||
accessKeyID, err := auth.GenerateAccessKeyID()
|
||||
if err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
reply.AccessKeyID = string(accessKeyID)
|
||||
|
||||
secretAccessKey, err := auth.GenerateSecretAccessKey()
|
||||
if err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
reply.SecretAccessKey = string(secretAccessKey)
|
||||
reply.Name = args.User
|
||||
|
||||
config.Users[args.User] = &auth.User{
|
||||
Name: args.User,
|
||||
AccessKeyID: string(accessKeyID),
|
||||
SecretAccessKey: string(secretAccessKey),
|
||||
}
|
||||
if err := auth.SaveConfig(config); err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchAuth fetch auth keys for a user
|
||||
func fetchAuth(args *AuthArgs, reply *AuthReply) *probe.Error {
|
||||
config, err := auth.LoadConfig()
|
||||
if err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
if _, ok := config.Users[args.User]; !ok {
|
||||
return probe.NewError(errors.New("User not found"))
|
||||
}
|
||||
reply.AccessKeyID = config.Users[args.User].AccessKeyID
|
||||
reply.SecretAccessKey = config.Users[args.User].SecretAccessKey
|
||||
reply.Name = args.User
|
||||
return nil
|
||||
}
|
||||
|
||||
// resetAuth reset auth keys for a user
|
||||
func resetAuth(args *AuthArgs, reply *AuthReply) *probe.Error {
|
||||
config, err := auth.LoadConfig()
|
||||
if err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
if _, ok := config.Users[args.User]; !ok {
|
||||
return probe.NewError(errors.New("User not found"))
|
||||
}
|
||||
accessKeyID, err := auth.GenerateAccessKeyID()
|
||||
if err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
reply.AccessKeyID = string(accessKeyID)
|
||||
secretAccessKey, err := auth.GenerateSecretAccessKey()
|
||||
if err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
reply.SecretAccessKey = string(secretAccessKey)
|
||||
reply.Name = args.User
|
||||
|
||||
config.Users[args.User] = &auth.User{
|
||||
Name: args.User,
|
||||
AccessKeyID: string(accessKeyID),
|
||||
SecretAccessKey: string(secretAccessKey),
|
||||
}
|
||||
return auth.SaveConfig(config).Trace()
|
||||
}
|
||||
|
||||
// Generate auth keys
|
||||
func (s *AuthService) Generate(r *http.Request, args *AuthArgs, reply *AuthReply) error {
|
||||
if strings.TrimSpace(args.User) == "" {
|
||||
return errors.New("Invalid argument")
|
||||
}
|
||||
if err := generateAuth(args, reply); err != nil {
|
||||
return probe.WrapError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch auth keys
|
||||
func (s *AuthService) Fetch(r *http.Request, args *AuthArgs, reply *AuthReply) error {
|
||||
if strings.TrimSpace(args.User) == "" {
|
||||
return errors.New("Invalid argument")
|
||||
}
|
||||
if err := fetchAuth(args, reply); err != nil {
|
||||
return probe.WrapError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset auth keys, generates new set of auth keys
|
||||
func (s *AuthService) Reset(r *http.Request, args *AuthArgs, reply *AuthReply) error {
|
||||
if strings.TrimSpace(args.User) == "" {
|
||||
return errors.New("Invalid argument")
|
||||
}
|
||||
if err := resetAuth(args, reply); err != nil {
|
||||
return probe.WrapError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Minio 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 rpc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
// DonutService donut service
|
||||
type DonutService struct{}
|
||||
|
||||
// DonutArgs collections of disks and name to initialize donut
|
||||
type DonutArgs struct {
|
||||
Name string
|
||||
MaxSize uint64
|
||||
Hostname string
|
||||
Disks []string
|
||||
}
|
||||
|
||||
// Reply reply for successful or failed Set operation
|
||||
type Reply struct {
|
||||
Message string `json:"message"`
|
||||
Error error `json:"error"`
|
||||
}
|
||||
|
||||
func setDonut(args *DonutArgs, reply *Reply) *probe.Error {
|
||||
conf := &donut.Config{Version: "0.0.1"}
|
||||
conf.DonutName = args.Name
|
||||
conf.MaxSize = args.MaxSize
|
||||
conf.NodeDiskMap = make(map[string][]string)
|
||||
conf.NodeDiskMap[args.Hostname] = args.Disks
|
||||
if err := donut.SaveConfig(conf); err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
reply.Message = "success"
|
||||
reply.Error = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set method
|
||||
func (s *DonutService) Set(r *http.Request, args *DonutArgs, reply *Reply) error {
|
||||
if err := setDonut(args, reply); err != nil {
|
||||
return probe.WrapError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Minio 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 rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/rpc/v2/json"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
// Operation RPC operation
|
||||
type Operation struct {
|
||||
Method string
|
||||
Request interface{}
|
||||
}
|
||||
|
||||
// Request rpc client request
|
||||
type Request struct {
|
||||
req *http.Request
|
||||
transport http.RoundTripper
|
||||
}
|
||||
|
||||
// NewRequest initiate a new client RPC request
|
||||
func NewRequest(url string, op Operation, transport http.RoundTripper) (*Request, *probe.Error) {
|
||||
params, err := json.EncodeClientRequest(op.Method, op.Request)
|
||||
if err != nil {
|
||||
return nil, probe.NewError(err)
|
||||
}
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(params))
|
||||
if err != nil {
|
||||
return nil, probe.NewError(err)
|
||||
}
|
||||
rpcReq := &Request{}
|
||||
rpcReq.req = req
|
||||
rpcReq.req.Header.Set("Content-Type", "application/json")
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
rpcReq.transport = transport
|
||||
return rpcReq, nil
|
||||
}
|
||||
|
||||
// Do - make a http connection
|
||||
func (r Request) Do() (*http.Response, *probe.Error) {
|
||||
resp, err := r.transport.RoundTrip(r.req)
|
||||
if err != nil {
|
||||
if err, ok := probe.UnwrapError(err); ok {
|
||||
return nil, err.Trace()
|
||||
}
|
||||
return nil, probe.NewError(err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Get - get value of requested header
|
||||
func (r Request) Get(key string) string {
|
||||
return r.req.Header.Get(key)
|
||||
}
|
||||
|
||||
// Set - set value of a header key
|
||||
func (r *Request) Set(key, value string) {
|
||||
r.req.Header.Set(key, value)
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Minio 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 rpc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
// MinioServer - container for minio server data
|
||||
type MinioServer struct {
|
||||
IP string `json:"ip"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// ServerArgs - server arg
|
||||
type ServerArgs struct {
|
||||
MinioServers []MinioServer `json:"servers"`
|
||||
}
|
||||
|
||||
// ServerAddReply - server add reply
|
||||
type ServerAddReply struct {
|
||||
ServersAdded []MinioServer `json:"serversAdded"`
|
||||
}
|
||||
|
||||
// MemStatsReply memory statistics
|
||||
type MemStatsReply struct {
|
||||
runtime.MemStats `json:"memstats"`
|
||||
}
|
||||
|
||||
// DiskStatsReply disk statistics
|
||||
type DiskStatsReply struct {
|
||||
DiskStats syscall.Statfs_t `json:"diskstats"`
|
||||
}
|
||||
|
||||
// SysInfoReply system info
|
||||
type SysInfoReply struct {
|
||||
Hostname string `json:"hostname"`
|
||||
SysARCH string `json:"sysArch"`
|
||||
SysOS string `json:"sysOS"`
|
||||
SysCPUS int `json:"sysNCPUs"`
|
||||
GORoutines int `json:"golangRoutines"`
|
||||
GOVersion string `json:"golangVersion"`
|
||||
}
|
||||
|
||||
// ServerListReply list of minio servers
|
||||
type ServerListReply struct {
|
||||
ServerList []MinioServer `json:"servers"`
|
||||
}
|
||||
|
||||
// ServerService server json rpc service
|
||||
type ServerService struct {
|
||||
serverList []MinioServer
|
||||
}
|
||||
|
||||
// Add - add new server
|
||||
func (s *ServerService) Add(r *http.Request, arg *ServerArgs, reply *ServerAddReply) error {
|
||||
for _, server := range arg.MinioServers {
|
||||
server.Status = "connected"
|
||||
reply.ServersAdded = append(reply.ServersAdded, server)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MemStats - memory statistics on the server
|
||||
func (s *ServerService) MemStats(r *http.Request, arg *ServerArgs, reply *MemStatsReply) error {
|
||||
runtime.ReadMemStats(&reply.MemStats)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiskStats - disk statistics on the server
|
||||
func (s *ServerService) DiskStats(r *http.Request, arg *ServerArgs, reply *DiskStatsReply) error {
|
||||
syscall.Statfs("/", &reply.DiskStats)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SysInfo - system info for the server
|
||||
func (s *ServerService) SysInfo(r *http.Request, arg *ServerArgs, reply *SysInfoReply) error {
|
||||
reply.SysOS = runtime.GOOS
|
||||
reply.SysARCH = runtime.GOARCH
|
||||
reply.SysCPUS = runtime.NumCPU()
|
||||
reply.GOVersion = runtime.Version()
|
||||
reply.GORoutines = runtime.NumGoroutine()
|
||||
var err error
|
||||
reply.Hostname, err = os.Hostname()
|
||||
if err != nil {
|
||||
return probe.WrapError(probe.NewError(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List of servers in the cluster
|
||||
func (s *ServerService) List(r *http.Request, arg *ServerArgs, reply *ServerListReply) error {
|
||||
reply.ServerList = []MinioServer{
|
||||
{
|
||||
"server.one",
|
||||
"192.168.1.1",
|
||||
"192.168.1.1",
|
||||
"connected",
|
||||
},
|
||||
{
|
||||
"server.two",
|
||||
"192.168.1.2",
|
||||
"192.168.1.2",
|
||||
"connected",
|
||||
},
|
||||
{
|
||||
"server.three",
|
||||
"192.168.1.3",
|
||||
"192.168.1.3",
|
||||
"connected",
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Minio 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 rpc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// VersionArgs basic json RPC params
|
||||
type VersionArgs struct{}
|
||||
|
||||
// VersionService get version service
|
||||
type VersionService struct{}
|
||||
|
||||
// VersionReply version reply
|
||||
type VersionReply struct {
|
||||
Version string `json:"version"`
|
||||
BuildDate string `json:"buildDate"`
|
||||
Architecture string `json:"arch"`
|
||||
OperatingSystem string `json:"os"`
|
||||
}
|
||||
|
||||
// Get version
|
||||
func (v *VersionService) Get(r *http.Request, args *VersionArgs, reply *VersionReply) error {
|
||||
reply.Version = "0.0.1"
|
||||
//TODO: Better approach needed here to pass global states like version. --ab.
|
||||
// reply.BuildDate = version.Version
|
||||
reply.Architecture = runtime.GOARCH
|
||||
reply.OperatingSystem = runtime.GOOS
|
||||
return nil
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2014 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 controller
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
jsonrpc "github.com/gorilla/rpc/v2/json"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/controller/rpc"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Hook up gocheck into the "go test" runner.
|
||||
func Test(t *testing.T) { TestingT(t) }
|
||||
|
||||
type MySuite struct{}
|
||||
|
||||
var _ = Suite(&MySuite{})
|
||||
|
||||
var testRPCServer *httptest.Server
|
||||
|
||||
func (s *MySuite) SetUpSuite(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "api-")
|
||||
c.Assert(err, IsNil)
|
||||
auth.SetAuthConfigPath(root)
|
||||
|
||||
testRPCServer = httptest.NewServer(getRPCHandler())
|
||||
}
|
||||
|
||||
func (s *MySuite) TearDownSuite(c *C) {
|
||||
testRPCServer.Close()
|
||||
}
|
||||
|
||||
func (s *MySuite) TestMemStats(c *C) {
|
||||
op := rpc.Operation{
|
||||
Method: "Server.MemStats",
|
||||
Request: rpc.ServerArgs{},
|
||||
}
|
||||
req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(req.Get("Content-Type"), Equals, "application/json")
|
||||
resp, err := req.Do()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var reply rpc.MemStatsReply
|
||||
c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil)
|
||||
resp.Body.Close()
|
||||
c.Assert(reply, Not(DeepEquals), rpc.MemStatsReply{})
|
||||
}
|
||||
|
||||
func (s *MySuite) TestSysInfo(c *C) {
|
||||
op := rpc.Operation{
|
||||
Method: "Server.SysInfo",
|
||||
Request: rpc.ServerArgs{},
|
||||
}
|
||||
req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(req.Get("Content-Type"), Equals, "application/json")
|
||||
resp, err := req.Do()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var reply rpc.SysInfoReply
|
||||
c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil)
|
||||
resp.Body.Close()
|
||||
c.Assert(reply, Not(DeepEquals), rpc.SysInfoReply{})
|
||||
}
|
||||
|
||||
func (s *MySuite) TestServerList(c *C) {
|
||||
op := rpc.Operation{
|
||||
Method: "Server.List",
|
||||
Request: rpc.ServerArgs{},
|
||||
}
|
||||
req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(req.Get("Content-Type"), Equals, "application/json")
|
||||
resp, err := req.Do()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var reply rpc.ServerListReply
|
||||
c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil)
|
||||
resp.Body.Close()
|
||||
c.Assert(reply, Not(DeepEquals), rpc.ServerListReply{})
|
||||
}
|
||||
|
||||
func (s *MySuite) TestServerAdd(c *C) {
|
||||
op := rpc.Operation{
|
||||
Method: "Server.Add",
|
||||
Request: rpc.ServerArgs{MinioServers: []rpc.MinioServer{}},
|
||||
}
|
||||
req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(req.Get("Content-Type"), Equals, "application/json")
|
||||
resp, err := req.Do()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var reply rpc.ServerAddReply
|
||||
c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil)
|
||||
resp.Body.Close()
|
||||
c.Assert(reply, Not(DeepEquals), rpc.ServerAddReply{ServersAdded: []rpc.MinioServer{}})
|
||||
}
|
||||
|
||||
func (s *MySuite) TestAuth(c *C) {
|
||||
op := rpc.Operation{
|
||||
Method: "Auth.Generate",
|
||||
Request: rpc.AuthArgs{User: "newuser"},
|
||||
}
|
||||
req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(req.Get("Content-Type"), Equals, "application/json")
|
||||
resp, err := req.Do()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var reply rpc.AuthReply
|
||||
c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &reply), IsNil)
|
||||
resp.Body.Close()
|
||||
c.Assert(reply, Not(DeepEquals), rpc.AuthReply{})
|
||||
c.Assert(len(reply.AccessKeyID), Equals, 20)
|
||||
c.Assert(len(reply.SecretAccessKey), Equals, 40)
|
||||
c.Assert(len(reply.Name), Not(Equals), 0)
|
||||
|
||||
op = rpc.Operation{
|
||||
Method: "Auth.Fetch",
|
||||
Request: rpc.AuthArgs{User: "newuser"},
|
||||
}
|
||||
req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(req.Get("Content-Type"), Equals, "application/json")
|
||||
resp, err = req.Do()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var newReply rpc.AuthReply
|
||||
c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &newReply), IsNil)
|
||||
resp.Body.Close()
|
||||
c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{})
|
||||
c.Assert(reply.AccessKeyID, Equals, newReply.AccessKeyID)
|
||||
c.Assert(reply.SecretAccessKey, Equals, newReply.SecretAccessKey)
|
||||
c.Assert(len(reply.Name), Not(Equals), 0)
|
||||
|
||||
op = rpc.Operation{
|
||||
Method: "Auth.Reset",
|
||||
Request: rpc.AuthArgs{User: "newuser"},
|
||||
}
|
||||
req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(req.Get("Content-Type"), Equals, "application/json")
|
||||
resp, err = req.Do()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var resetReply rpc.AuthReply
|
||||
c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &resetReply), IsNil)
|
||||
resp.Body.Close()
|
||||
c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{})
|
||||
c.Assert(reply.AccessKeyID, Not(Equals), resetReply.AccessKeyID)
|
||||
c.Assert(reply.SecretAccessKey, Not(Equals), resetReply.SecretAccessKey)
|
||||
c.Assert(len(reply.Name), Not(Equals), 0)
|
||||
|
||||
// these operations should fail
|
||||
|
||||
/// generating access for existing user fails
|
||||
op = rpc.Operation{
|
||||
Method: "Auth.Generate",
|
||||
Request: rpc.AuthArgs{User: "newuser"},
|
||||
}
|
||||
req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(req.Get("Content-Type"), Equals, "application/json")
|
||||
resp, err = req.Do()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
||||
|
||||
/// null user provided invalid
|
||||
op = rpc.Operation{
|
||||
Method: "Auth.Generate",
|
||||
Request: rpc.AuthArgs{User: ""},
|
||||
}
|
||||
req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(req.Get("Content-Type"), Equals, "application/json")
|
||||
resp, err = req.Do()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Minio 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 controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/minhttp"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
// getRPCServer instance
|
||||
func getRPCServer(rpcHandler http.Handler) (*http.Server, *probe.Error) {
|
||||
// Minio server config
|
||||
httpServer := &http.Server{
|
||||
Addr: ":9001", // TODO make this configurable
|
||||
Handler: rpcHandler,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
var hosts []string
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, probe.NewError(err)
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if addr.Network() == "ip+net" {
|
||||
host := strings.Split(addr.String(), "/")[0]
|
||||
if ip := net.ParseIP(host); ip.To4() != nil {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, host := range hosts {
|
||||
fmt.Printf("Starting minio server on: http://%s:9001/rpc, PID: %d\n", host, os.Getpid())
|
||||
}
|
||||
return httpServer, nil
|
||||
}
|
||||
|
||||
// Start starts a controller
|
||||
func Start() *probe.Error {
|
||||
rpcServer, err := getRPCServer(getRPCHandler())
|
||||
if err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
// Setting rate limit to 'zero' no ratelimiting implemented
|
||||
if err := minhttp.ListenAndServeLimited(0, rpcServer); err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Please read for more information - http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
|
||||
//
|
||||
// Here We are only supporting 'acl's through request headers not through their request body
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#setting-acls
|
||||
|
||||
// Minio only supports three types for now i.e 'private, public-read, public-read-write'
|
||||
|
||||
// ACLType - different acl types
|
||||
type ACLType int
|
||||
|
||||
const (
|
||||
unsupportedACLType ACLType = iota
|
||||
privateACLType
|
||||
publicReadACLType
|
||||
publicReadWriteACLType
|
||||
)
|
||||
|
||||
// Get acl type requested from 'x-amz-acl' header
|
||||
func getACLType(req *http.Request) ACLType {
|
||||
aclHeader := req.Header.Get("x-amz-acl")
|
||||
if aclHeader != "" {
|
||||
switch {
|
||||
case aclHeader == "private":
|
||||
return privateACLType
|
||||
case aclHeader == "public-read":
|
||||
return publicReadACLType
|
||||
case aclHeader == "public-read-write":
|
||||
return publicReadWriteACLType
|
||||
default:
|
||||
return unsupportedACLType
|
||||
}
|
||||
}
|
||||
// make it default private
|
||||
return privateACLType
|
||||
}
|
||||
|
||||
// ACL type to human readable string
|
||||
func getACLTypeString(acl ACLType) string {
|
||||
switch acl {
|
||||
case privateACLType:
|
||||
{
|
||||
return "private"
|
||||
}
|
||||
case publicReadACLType:
|
||||
{
|
||||
return "public-read"
|
||||
}
|
||||
case publicReadWriteACLType:
|
||||
{
|
||||
return "public-read-write"
|
||||
}
|
||||
case unsupportedACLType:
|
||||
{
|
||||
return ""
|
||||
}
|
||||
default:
|
||||
return "private"
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import "github.com/minio/minio/pkg/donut"
|
||||
|
||||
// Operation container for individual operations read by Ticket Master
|
||||
type Operation struct {
|
||||
ProceedCh chan struct{}
|
||||
}
|
||||
|
||||
// Minio container for API and also carries OP (operation) channel
|
||||
type Minio struct {
|
||||
OP chan Operation
|
||||
Donut donut.Interface
|
||||
}
|
||||
|
||||
// New instantiate a new minio API
|
||||
func New() Minio {
|
||||
// ignore errors for now
|
||||
d, err := donut.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Minio{
|
||||
OP: make(chan Operation),
|
||||
Donut: d,
|
||||
}
|
||||
}
|
||||
@@ -1,440 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
func (api Minio) isValidOp(w http.ResponseWriter, req *http.Request, acceptsContentType contentType) bool {
|
||||
vars := mux.Vars(req)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
bucketMetadata, err := api.Donut.GetBucketMetadata(bucket, nil)
|
||||
if err != nil {
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.BucketNotFound:
|
||||
writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path)
|
||||
return false
|
||||
case donut.BucketNameInvalid:
|
||||
writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path)
|
||||
return false
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if _, err = stripAccessKeyID(req.Header.Get("Authorization")); err != nil {
|
||||
if bucketMetadata.ACL.IsPrivate() {
|
||||
return true
|
||||
//uncomment this when we have webcli
|
||||
//writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path)
|
||||
//return false
|
||||
}
|
||||
if bucketMetadata.ACL.IsPublicRead() && req.Method == "PUT" {
|
||||
return true
|
||||
//uncomment this when we have webcli
|
||||
//writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path)
|
||||
//return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ListMultipartUploadsHandler - GET Bucket (List Multipart uploads)
|
||||
// -------------------------
|
||||
// This operation lists in-progress multipart uploads. An in-progress
|
||||
// multipart upload is a multipart upload that has been initiated,
|
||||
// using the Initiate Multipart Upload request, but has not yet been completed or aborted.
|
||||
// This operation returns at most 1,000 multipart uploads in the response.
|
||||
//
|
||||
func (api Minio) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
if !api.isValidOp(w, req, acceptsContentType) {
|
||||
return
|
||||
}
|
||||
|
||||
resources := getBucketMultipartResources(req.URL.Query())
|
||||
if resources.MaxUploads < 0 {
|
||||
writeErrorResponse(w, req, InvalidMaxUploads, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
if resources.MaxUploads == 0 {
|
||||
resources.MaxUploads = maxObjectList
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resources, err := api.Donut.ListMultipartUploads(bucket, resources, signature)
|
||||
if err == nil {
|
||||
// generate response
|
||||
response := generateListMultipartUploadsResponse(bucket, resources)
|
||||
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
||||
// write headers
|
||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
||||
// write body
|
||||
w.Write(encodedSuccessResponse)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNotFound:
|
||||
writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// ListObjectsHandler - GET Bucket (List Objects)
|
||||
// -------------------------
|
||||
// This implementation of the GET operation returns 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.
|
||||
//
|
||||
func (api Minio) ListObjectsHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
if !api.isValidOp(w, req, acceptsContentType) {
|
||||
return
|
||||
}
|
||||
|
||||
if isRequestUploads(req.URL.Query()) {
|
||||
api.ListMultipartUploadsHandler(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
resources := getBucketResources(req.URL.Query())
|
||||
if resources.Maxkeys < 0 {
|
||||
writeErrorResponse(w, req, InvalidMaxKeys, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
if resources.Maxkeys == 0 {
|
||||
resources.Maxkeys = maxObjectList
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
objects, resources, err := api.Donut.ListObjects(bucket, resources, signature)
|
||||
if err == nil {
|
||||
// generate response
|
||||
response := generateListObjectsResponse(bucket, objects, resources)
|
||||
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
||||
// write headers
|
||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
||||
// write body
|
||||
w.Write(encodedSuccessResponse)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNameInvalid:
|
||||
writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNotFound:
|
||||
writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path)
|
||||
case donut.ObjectNotFound:
|
||||
writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path)
|
||||
case donut.ObjectNameInvalid:
|
||||
writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// ListBucketsHandler - GET Service
|
||||
// -----------
|
||||
// This implementation of the GET operation returns a list of all buckets
|
||||
// owned by the authenticated sender of the request.
|
||||
func (api Minio) ListBucketsHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
// uncomment this when we have webcli
|
||||
// without access key credentials one cannot list buckets
|
||||
// if _, err := StripAccessKeyID(req); err != nil {
|
||||
// writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path)
|
||||
// return
|
||||
// }
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
buckets, err := api.Donut.ListBuckets(signature)
|
||||
if err == nil {
|
||||
// generate response
|
||||
response := generateListBucketsResponse(buckets)
|
||||
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
||||
// write headers
|
||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
||||
// write response
|
||||
w.Write(encodedSuccessResponse)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// PutBucketHandler - PUT Bucket
|
||||
// ----------
|
||||
// This implementation of the PUT operation creates a new bucket for authenticated request
|
||||
func (api Minio) PutBucketHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
// uncomment this when we have webcli
|
||||
// without access key credentials one cannot create a bucket
|
||||
// if _, err := StripAccessKeyID(req); err != nil {
|
||||
// writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path)
|
||||
// return
|
||||
// }
|
||||
|
||||
if isRequestBucketACL(req.URL.Query()) {
|
||||
api.PutBucketACLHandler(w, req)
|
||||
return
|
||||
}
|
||||
// read from 'x-amz-acl'
|
||||
aclType := getACLType(req)
|
||||
if aclType == unsupportedACLType {
|
||||
writeErrorResponse(w, req, NotImplemented, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// if body of request is non-nil then check for validity of Content-Length
|
||||
if req.Body != nil {
|
||||
/// if Content-Length missing, deny the request
|
||||
size := req.Header.Get("Content-Length")
|
||||
if size == "" {
|
||||
writeErrorResponse(w, req, MissingContentLength, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err := api.Donut.MakeBucket(bucket, getACLTypeString(aclType), req.Body, signature)
|
||||
if err == nil {
|
||||
// Make sure to add Location information here only for bucket
|
||||
w.Header().Set("Location", "/"+bucket)
|
||||
writeSuccessResponse(w, acceptsContentType)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.TooManyBuckets:
|
||||
writeErrorResponse(w, req, TooManyBuckets, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNameInvalid:
|
||||
writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketExists:
|
||||
writeErrorResponse(w, req, BucketAlreadyExists, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// PutBucketACLHandler - PUT Bucket ACL
|
||||
// ----------
|
||||
// This implementation of the PUT operation modifies the bucketACL for authenticated request
|
||||
func (api Minio) PutBucketACLHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
|
||||
// read from 'x-amz-acl'
|
||||
aclType := getACLType(req)
|
||||
if aclType == unsupportedACLType {
|
||||
writeErrorResponse(w, req, NotImplemented, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err := api.Donut.SetBucketMetadata(bucket, map[string]string{"acl": getACLTypeString(aclType)}, signature)
|
||||
if err == nil {
|
||||
writeSuccessResponse(w, acceptsContentType)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNameInvalid:
|
||||
writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNotFound:
|
||||
writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// HeadBucketHandler - HEAD Bucket
|
||||
// ----------
|
||||
// This operation is useful to determine if a bucket exists.
|
||||
// The operation returns a 200 OK if the bucket exists and you
|
||||
// have permission to access it. Otherwise, the operation might
|
||||
// return responses such as 404 Not Found and 403 Forbidden.
|
||||
func (api Minio) HeadBucketHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
|
||||
vars := mux.Vars(req)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err := api.Donut.GetBucketMetadata(bucket, signature)
|
||||
if err == nil {
|
||||
writeSuccessResponse(w, acceptsContentType)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNotFound:
|
||||
writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNameInvalid:
|
||||
writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import "net/http"
|
||||
|
||||
type contentType int
|
||||
|
||||
const (
|
||||
unknownContentType contentType = iota
|
||||
xmlContentType
|
||||
jsonContentType
|
||||
)
|
||||
|
||||
// Get content type requested from 'Accept' header
|
||||
func getContentType(req *http.Request) contentType {
|
||||
acceptHeader := req.Header.Get("Accept")
|
||||
switch {
|
||||
case acceptHeader == "application/json":
|
||||
return jsonContentType
|
||||
default:
|
||||
return xmlContentType
|
||||
}
|
||||
}
|
||||
|
||||
// Content type to human readable string
|
||||
func getContentTypeString(content contentType) string {
|
||||
switch content {
|
||||
case jsonContentType:
|
||||
{
|
||||
return "application/json"
|
||||
}
|
||||
case xmlContentType:
|
||||
{
|
||||
return "application/xml"
|
||||
}
|
||||
default:
|
||||
{
|
||||
return "application/xml"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2014 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 api
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// Config - http server config
|
||||
type Config struct {
|
||||
Address string
|
||||
TLS bool
|
||||
CertFile string
|
||||
KeyFile string
|
||||
RateLimit int
|
||||
}
|
||||
|
||||
// Limit number of objects in a given response
|
||||
const (
|
||||
maxObjectList = 1000
|
||||
)
|
||||
|
||||
// ListObjectsResponse - format for list objects response
|
||||
type ListObjectsResponse struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
|
||||
|
||||
CommonPrefixes []*CommonPrefix
|
||||
Contents []*Object
|
||||
|
||||
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 int
|
||||
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. Server 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
|
||||
}
|
||||
|
||||
// Part container for part metadata
|
||||
type Part struct {
|
||||
PartNumber int
|
||||
ETag string
|
||||
LastModified string
|
||||
Size int64
|
||||
}
|
||||
|
||||
// ListPartsResponse - format for list parts response
|
||||
type ListPartsResponse struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListPartsResult" json:"-"`
|
||||
|
||||
Bucket string
|
||||
Key string
|
||||
UploadID string `xml:"UploadId"`
|
||||
|
||||
Initiator Initiator
|
||||
Owner Owner
|
||||
|
||||
// The class of storage used to store the object.
|
||||
StorageClass string
|
||||
|
||||
PartNumberMarker int
|
||||
NextPartNumberMarker int
|
||||
MaxParts int
|
||||
IsTruncated bool
|
||||
|
||||
// List of parts
|
||||
Part []*Part
|
||||
}
|
||||
|
||||
// ListMultipartUploadsResponse - format for list multipart uploads response
|
||||
type ListMultipartUploadsResponse struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListMultipartUploadsResult" json:"-"`
|
||||
|
||||
Bucket string
|
||||
KeyMarker string
|
||||
UploadIDMarker string `xml:"UploadIdMarker"`
|
||||
NextKeyMarker string
|
||||
NextUploadIDMarker string `xml:"NextUploadIdMarker"`
|
||||
EncodingType string
|
||||
MaxUploads int
|
||||
IsTruncated bool
|
||||
Upload []*Upload
|
||||
Prefix string
|
||||
Delimiter string
|
||||
CommonPrefixes []*CommonPrefix
|
||||
}
|
||||
|
||||
// ListBucketsResponse - format for list buckets response
|
||||
type ListBucketsResponse struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListAllMyBucketsResult" json:"-"`
|
||||
// Container for one or more buckets.
|
||||
Buckets struct {
|
||||
Bucket []*Bucket
|
||||
} // Buckets are nested
|
||||
Owner Owner
|
||||
}
|
||||
|
||||
// Upload container for in progress multipart upload
|
||||
type Upload struct {
|
||||
Key string
|
||||
UploadID string `xml:"UploadId"`
|
||||
Initiator Initiator
|
||||
Owner Owner
|
||||
StorageClass string
|
||||
Initiated string
|
||||
}
|
||||
|
||||
// CommonPrefix container for prefix response in ListObjectsResponse
|
||||
type CommonPrefix struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// Bucket container for bucket metadata
|
||||
type Bucket struct {
|
||||
Name string
|
||||
CreationDate string
|
||||
}
|
||||
|
||||
// Object container for object metadata
|
||||
type Object struct {
|
||||
ETag string
|
||||
Key string
|
||||
LastModified string
|
||||
Size int64
|
||||
|
||||
Owner Owner
|
||||
|
||||
// The class of storage used to store the object.
|
||||
StorageClass string
|
||||
}
|
||||
|
||||
// Initiator inherit from Owner struct, fields are same
|
||||
type Initiator Owner
|
||||
|
||||
// Owner - bucket owner/principal
|
||||
type Owner struct {
|
||||
ID string
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
// InitiateMultipartUploadResponse container for InitiateMultiPartUpload response, provides uploadID to start MultiPart upload
|
||||
type InitiateMultipartUploadResponse struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ InitiateMultipartUploadResult" json:"-"`
|
||||
|
||||
Bucket string
|
||||
Key string
|
||||
UploadID string `xml:"UploadId"`
|
||||
}
|
||||
|
||||
// CompleteMultipartUploadResponse container for completed multipart upload response
|
||||
type CompleteMultipartUploadResponse struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUploadResult" json:"-"`
|
||||
|
||||
Location string
|
||||
Bucket string
|
||||
Key string
|
||||
ETag string
|
||||
}
|
||||
|
||||
// List of not implemented bucket queries
|
||||
var notimplementedBucketResourceNames = map[string]bool{
|
||||
"policy": true,
|
||||
"cors": true,
|
||||
"lifecycle": true,
|
||||
"location": true,
|
||||
"logging": true,
|
||||
"notification": true,
|
||||
"tagging": true,
|
||||
"versions": true,
|
||||
"requestPayment": true,
|
||||
"versioning": true,
|
||||
"website": true,
|
||||
}
|
||||
|
||||
// List of not implemented object queries
|
||||
var notimplementedObjectResourceNames = map[string]bool{
|
||||
"torrent": true,
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Error structure
|
||||
type Error struct {
|
||||
Code string
|
||||
Description string
|
||||
HTTPStatusCode int
|
||||
}
|
||||
|
||||
// ErrorResponse - error response format
|
||||
type ErrorResponse struct {
|
||||
XMLName xml.Name `xml:"Error" json:"-"`
|
||||
Code string
|
||||
Message string
|
||||
Resource string
|
||||
RequestID string `xml:"RequestId"`
|
||||
HostID string `xml:"HostId"`
|
||||
}
|
||||
|
||||
// Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
||||
const (
|
||||
AccessDenied = iota
|
||||
BadDigest
|
||||
BucketAlreadyExists
|
||||
EntityTooSmall
|
||||
EntityTooLarge
|
||||
IncompleteBody
|
||||
InternalError
|
||||
InvalidAccessKeyID
|
||||
InvalidBucketName
|
||||
InvalidDigest
|
||||
InvalidRange
|
||||
InvalidRequest
|
||||
InvalidMaxKeys
|
||||
InvalidMaxUploads
|
||||
InvalidMaxParts
|
||||
InvalidPartNumberMarker
|
||||
MalformedXML
|
||||
MissingContentLength
|
||||
MissingRequestBodyError
|
||||
NoSuchBucket
|
||||
NoSuchKey
|
||||
NoSuchUpload
|
||||
NotImplemented
|
||||
RequestTimeTooSkewed
|
||||
SignatureDoesNotMatch
|
||||
TooManyBuckets
|
||||
MethodNotAllowed
|
||||
InvalidPart
|
||||
InvalidPartOrder
|
||||
AuthorizationHeaderMalformed
|
||||
)
|
||||
|
||||
// Error codes, non exhaustive list - standard HTTP errors
|
||||
const (
|
||||
NotAcceptable = iota + 30
|
||||
)
|
||||
|
||||
// Error code to Error structure map
|
||||
var errorCodeResponse = map[int]Error{
|
||||
InvalidMaxUploads: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument maxUploads must be an integer between 0 and 2147483647.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
InvalidMaxKeys: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument maxKeys must be an integer between 0 and 2147483647.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
InvalidMaxParts: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument maxParts must be an integer between 1 and 10000.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
InvalidPartNumberMarker: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument partNumberMarker must be an integer.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
AccessDenied: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Access Denied.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
BadDigest: {
|
||||
Code: "BadDigest",
|
||||
Description: "The Content-MD5 you specified did not match what we received.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
BucketAlreadyExists: {
|
||||
Code: "BucketAlreadyExists",
|
||||
Description: "The requested bucket name is not available.",
|
||||
HTTPStatusCode: http.StatusConflict,
|
||||
},
|
||||
EntityTooSmall: {
|
||||
Code: "EntityTooSmall",
|
||||
Description: "Your proposed upload is smaller than the minimum allowed object size.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
EntityTooLarge: {
|
||||
Code: "EntityTooLarge",
|
||||
Description: "Your proposed upload exceeds the maximum allowed object size.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
IncompleteBody: {
|
||||
Code: "IncompleteBody",
|
||||
Description: "You did not provide the number of bytes specified by the Content-Length HTTP header.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
InternalError: {
|
||||
Code: "InternalError",
|
||||
Description: "We encountered an internal error, please try again.",
|
||||
HTTPStatusCode: http.StatusInternalServerError,
|
||||
},
|
||||
InvalidAccessKeyID: {
|
||||
Code: "InvalidAccessKeyID",
|
||||
Description: "The access key ID you provided does not exist in our records.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
InvalidBucketName: {
|
||||
Code: "InvalidBucketName",
|
||||
Description: "The specified bucket is not valid.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
InvalidDigest: {
|
||||
Code: "InvalidDigest",
|
||||
Description: "The Content-MD5 you specified is not valid.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
InvalidRange: {
|
||||
Code: "InvalidRange",
|
||||
Description: "The requested range cannot be satisfied.",
|
||||
HTTPStatusCode: http.StatusRequestedRangeNotSatisfiable,
|
||||
},
|
||||
MalformedXML: {
|
||||
Code: "MalformedXML",
|
||||
Description: "The XML you provided was not well-formed or did not validate against our published schema.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
MissingContentLength: {
|
||||
Code: "MissingContentLength",
|
||||
Description: "You must provide the Content-Length HTTP header.",
|
||||
HTTPStatusCode: http.StatusLengthRequired,
|
||||
},
|
||||
MissingRequestBodyError: {
|
||||
Code: "MissingRequestBodyError",
|
||||
Description: "Request body is empty.",
|
||||
HTTPStatusCode: http.StatusLengthRequired,
|
||||
},
|
||||
NoSuchBucket: {
|
||||
Code: "NoSuchBucket",
|
||||
Description: "The specified bucket does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
NoSuchKey: {
|
||||
Code: "NoSuchKey",
|
||||
Description: "The specified key does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
NoSuchUpload: {
|
||||
Code: "NoSuchUpload",
|
||||
Description: "The specified multipart upload does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
NotImplemented: {
|
||||
Code: "NotImplemented",
|
||||
Description: "A header you provided implies functionality that is not implemented.",
|
||||
HTTPStatusCode: http.StatusNotImplemented,
|
||||
},
|
||||
RequestTimeTooSkewed: {
|
||||
Code: "RequestTimeTooSkewed",
|
||||
Description: "The difference between the request time and the server's time is too large.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
SignatureDoesNotMatch: {
|
||||
Code: "SignatureDoesNotMatch",
|
||||
Description: "The request signature we calculated does not match the signature you provided.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
TooManyBuckets: {
|
||||
Code: "TooManyBuckets",
|
||||
Description: "You have attempted to create more buckets than allowed.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
MethodNotAllowed: {
|
||||
Code: "MethodNotAllowed",
|
||||
Description: "The specified method is not allowed against this resource.",
|
||||
HTTPStatusCode: http.StatusMethodNotAllowed,
|
||||
},
|
||||
NotAcceptable: {
|
||||
Code: "NotAcceptable",
|
||||
Description: "The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request.",
|
||||
HTTPStatusCode: http.StatusNotAcceptable,
|
||||
},
|
||||
InvalidPart: {
|
||||
Code: "InvalidPart",
|
||||
Description: "One or more of the specified parts could not be found.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
InvalidPartOrder: {
|
||||
Code: "InvalidPartOrder",
|
||||
Description: "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
AuthorizationHeaderMalformed: {
|
||||
Code: "AuthorizationHeaderMalformed",
|
||||
Description: "The authorization header is malformed; the region is wrong; expecting 'milkyway'.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
// errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown
|
||||
func getErrorCode(code int) Error {
|
||||
return errorCodeResponse[code]
|
||||
}
|
||||
|
||||
// getErrorResponse gets in standard error and resource value and
|
||||
// provides a encodable populated response values
|
||||
func getErrorResponse(err Error, resource string) ErrorResponse {
|
||||
var data = ErrorResponse{}
|
||||
data.Code = err.Code
|
||||
data.Message = err.Description
|
||||
if resource != "" {
|
||||
data.Resource = resource
|
||||
}
|
||||
// TODO implement this in future
|
||||
data.RequestID = "3L137"
|
||||
data.HostID = "3L137"
|
||||
|
||||
return data
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
// MiddlewareHandler - useful to chain different middleware http.Handler
|
||||
type MiddlewareHandler func(http.Handler) http.Handler
|
||||
|
||||
type contentTypeHandler struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
type timeHandler struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
type validateAuthHandler struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
type resourceHandler struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
const (
|
||||
iso8601Format = "20060102T150405Z"
|
||||
)
|
||||
|
||||
func parseDate(req *http.Request) (time.Time, error) {
|
||||
amzDate := req.Header.Get(http.CanonicalHeaderKey("x-amz-date"))
|
||||
switch {
|
||||
case amzDate != "":
|
||||
if _, err := time.Parse(time.RFC1123, amzDate); err == nil {
|
||||
return time.Parse(time.RFC1123, amzDate)
|
||||
}
|
||||
if _, err := time.Parse(time.RFC1123Z, amzDate); err == nil {
|
||||
return time.Parse(time.RFC1123Z, amzDate)
|
||||
}
|
||||
if _, err := time.Parse(iso8601Format, amzDate); err == nil {
|
||||
return time.Parse(iso8601Format, amzDate)
|
||||
}
|
||||
}
|
||||
date := req.Header.Get("Date")
|
||||
switch {
|
||||
case date != "":
|
||||
if _, err := time.Parse(time.RFC1123, date); err == nil {
|
||||
return time.Parse(time.RFC1123, date)
|
||||
}
|
||||
if _, err := time.Parse(time.RFC1123Z, date); err == nil {
|
||||
return time.Parse(time.RFC1123Z, date)
|
||||
}
|
||||
if _, err := time.Parse(iso8601Format, amzDate); err == nil {
|
||||
return time.Parse(iso8601Format, amzDate)
|
||||
}
|
||||
}
|
||||
return time.Time{}, errors.New("invalid request")
|
||||
}
|
||||
|
||||
// ValidContentTypeHandler to validate Accept type
|
||||
func ValidContentTypeHandler(h http.Handler) http.Handler {
|
||||
return contentTypeHandler{h}
|
||||
}
|
||||
|
||||
func (h contentTypeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
acceptsContentType := getContentType(r)
|
||||
if acceptsContentType == unknownContentType {
|
||||
writeErrorResponse(w, r, NotAcceptable, acceptsContentType, r.URL.Path)
|
||||
return
|
||||
}
|
||||
h.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// TimeValidityHandler to validate parsable time over http header
|
||||
func TimeValidityHandler(h http.Handler) http.Handler {
|
||||
return timeHandler{h}
|
||||
}
|
||||
|
||||
func (h timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
acceptsContentType := getContentType(r)
|
||||
// Verify if date headers are set, if not reject the request
|
||||
if r.Header.Get("Authorization") != "" {
|
||||
if r.Header.Get(http.CanonicalHeaderKey("x-amz-date")) == "" && r.Header.Get("Date") == "" {
|
||||
// there is no way to knowing if this is a valid request, could be a attack reject such clients
|
||||
writeErrorResponse(w, r, RequestTimeTooSkewed, acceptsContentType, r.URL.Path)
|
||||
return
|
||||
}
|
||||
date, err := parseDate(r)
|
||||
if err != nil {
|
||||
// there is no way to knowing if this is a valid request, could be a attack reject such clients
|
||||
writeErrorResponse(w, r, RequestTimeTooSkewed, acceptsContentType, r.URL.Path)
|
||||
return
|
||||
}
|
||||
duration := time.Since(date)
|
||||
minutes := time.Duration(5) * time.Minute
|
||||
if duration.Minutes() > minutes.Minutes() {
|
||||
writeErrorResponse(w, r, RequestTimeTooSkewed, acceptsContentType, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
h.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ValidateAuthHeaderHandler - validate auth header handler is wrapper handler used
|
||||
// for request validation with authorization header. Current authorization layer
|
||||
// supports S3's standard HMAC based signature request.
|
||||
func ValidateAuthHeaderHandler(h http.Handler) http.Handler {
|
||||
return validateAuthHandler{h}
|
||||
}
|
||||
|
||||
// validate auth header handler ServeHTTP() wrapper
|
||||
func (h validateAuthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
acceptsContentType := getContentType(r)
|
||||
accessKeyID, err := stripAccessKeyID(r.Header.Get("Authorization"))
|
||||
switch err.ToGoError() {
|
||||
case errInvalidRegion:
|
||||
writeErrorResponse(w, r, AuthorizationHeaderMalformed, acceptsContentType, r.URL.Path)
|
||||
return
|
||||
case errAccessKeyIDInvalid:
|
||||
writeErrorResponse(w, r, InvalidAccessKeyID, acceptsContentType, r.URL.Path)
|
||||
return
|
||||
case nil:
|
||||
// load auth config
|
||||
authConfig, err := auth.LoadConfig()
|
||||
if err != nil {
|
||||
writeErrorResponse(w, r, InternalError, acceptsContentType, r.URL.Path)
|
||||
return
|
||||
}
|
||||
// Access key not found
|
||||
for _, user := range authConfig.Users {
|
||||
if user.AccessKeyID == accessKeyID {
|
||||
h.handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
writeErrorResponse(w, r, InvalidAccessKeyID, acceptsContentType, r.URL.Path)
|
||||
return
|
||||
// All other errors for now, serve them
|
||||
default:
|
||||
// control reaches here, we should just send the request up the stack - internally
|
||||
// individual calls will validate themselves against un-authenticated requests
|
||||
h.handler.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// CorsHandler handler for CORS (Cross Origin Resource Sharing)
|
||||
func CorsHandler(h http.Handler) http.Handler {
|
||||
return cors.Default().Handler(h)
|
||||
}
|
||||
|
||||
// IgnoreResourcesHandler -
|
||||
// Ignore resources handler is wrapper handler used for API request resource validation
|
||||
// Since we do not support all the S3 queries, it is necessary for us to throw back a
|
||||
// valid error message indicating such a feature is not implemented.
|
||||
func IgnoreResourcesHandler(h http.Handler) http.Handler {
|
||||
return resourceHandler{h}
|
||||
}
|
||||
|
||||
// Resource handler ServeHTTP() wrapper
|
||||
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
acceptsContentType := getContentType(r)
|
||||
if ignoreNotImplementedObjectResources(r) || ignoreNotImplementedBucketResources(r) {
|
||||
writeErrorResponse(w, r, NotImplemented, acceptsContentType, r.URL.Path)
|
||||
return
|
||||
}
|
||||
h.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
//// helpers
|
||||
|
||||
// Checks requests for not implemented Bucket resources
|
||||
func ignoreNotImplementedBucketResources(req *http.Request) bool {
|
||||
q := req.URL.Query()
|
||||
for name := range q {
|
||||
if notimplementedBucketResourceNames[name] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks requests for not implemented Object resources
|
||||
func ignoreNotImplementedObjectResources(req *http.Request) bool {
|
||||
q := req.URL.Query()
|
||||
for name := range q {
|
||||
if notimplementedObjectResourceNames[name] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
)
|
||||
|
||||
// No encoder interface exists, so we create one.
|
||||
type encoder interface {
|
||||
Encode(v interface{}) error
|
||||
}
|
||||
|
||||
//// helpers
|
||||
|
||||
// Static alphaNumeric table used for generating unique request ids
|
||||
var alphaNumericTable = []byte("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
|
||||
// generateRequestID generate request id
|
||||
func generateRequestID() []byte {
|
||||
alpha := make([]byte, 16)
|
||||
rand.Read(alpha)
|
||||
for i := 0; i < 16; i++ {
|
||||
alpha[i] = alphaNumericTable[alpha[i]%byte(len(alphaNumericTable))]
|
||||
}
|
||||
return alpha
|
||||
}
|
||||
|
||||
// Write http common headers
|
||||
func setCommonHeaders(w http.ResponseWriter, acceptsType string, contentLength int) {
|
||||
// set unique request ID for each reply
|
||||
w.Header().Set("X-Amz-Request-Id", string(generateRequestID()))
|
||||
|
||||
// TODO: Modularity comes in the way of passing global state like "version". A better approach needed here. -ab
|
||||
// w.Header().Set("Server", ("Minio/" + version + " (" + runtime.GOOS + ";" + runtime.GOARCH + ")"))
|
||||
|
||||
w.Header().Set("Accept-Ranges", "bytes")
|
||||
w.Header().Set("Content-Type", acceptsType)
|
||||
w.Header().Set("Connection", "close")
|
||||
// should be set to '0' by default
|
||||
w.Header().Set("Content-Length", strconv.Itoa(contentLength))
|
||||
}
|
||||
|
||||
// Write error response headers
|
||||
func encodeErrorResponse(response interface{}, acceptsType contentType) []byte {
|
||||
var bytesBuffer bytes.Buffer
|
||||
var e encoder
|
||||
// write common headers
|
||||
switch acceptsType {
|
||||
case xmlContentType:
|
||||
e = xml.NewEncoder(&bytesBuffer)
|
||||
case jsonContentType:
|
||||
e = json.NewEncoder(&bytesBuffer)
|
||||
// by default even if unknown Accept header received handle it by sending XML contenttype response
|
||||
default:
|
||||
e = xml.NewEncoder(&bytesBuffer)
|
||||
}
|
||||
e.Encode(response)
|
||||
return bytesBuffer.Bytes()
|
||||
}
|
||||
|
||||
// Write object header
|
||||
func setObjectHeaders(w http.ResponseWriter, metadata donut.ObjectMetadata, contentRange *httpRange) {
|
||||
// set common headers
|
||||
if contentRange != nil {
|
||||
if contentRange.length > 0 {
|
||||
setCommonHeaders(w, metadata.Metadata["contentType"], int(contentRange.length))
|
||||
} else {
|
||||
setCommonHeaders(w, metadata.Metadata["contentType"], int(metadata.Size))
|
||||
}
|
||||
} else {
|
||||
setCommonHeaders(w, metadata.Metadata["contentType"], int(metadata.Size))
|
||||
}
|
||||
// set object headers
|
||||
lastModified := metadata.Created.Format(http.TimeFormat)
|
||||
// object related headers
|
||||
w.Header().Set("ETag", "\""+metadata.MD5Sum+"\"")
|
||||
w.Header().Set("Last-Modified", lastModified)
|
||||
|
||||
// set content range
|
||||
if contentRange != nil {
|
||||
if contentRange.start > 0 || contentRange.length > 0 {
|
||||
w.Header().Set("Content-Range", contentRange.String())
|
||||
w.WriteHeader(http.StatusPartialContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func encodeSuccessResponse(response interface{}, acceptsType contentType) []byte {
|
||||
var e encoder
|
||||
var bytesBuffer bytes.Buffer
|
||||
switch acceptsType {
|
||||
case xmlContentType:
|
||||
e = xml.NewEncoder(&bytesBuffer)
|
||||
case jsonContentType:
|
||||
e = json.NewEncoder(&bytesBuffer)
|
||||
}
|
||||
e.Encode(response)
|
||||
return bytesBuffer.Bytes()
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logHandler struct {
|
||||
handler http.Handler
|
||||
logger chan<- []byte
|
||||
}
|
||||
|
||||
// logMessage is a serializable json log message
|
||||
type logMessage struct {
|
||||
StartTime time.Time
|
||||
Duration time.Duration
|
||||
StatusMessage string // human readable http status message
|
||||
|
||||
// HTTP detailed message
|
||||
HTTP struct {
|
||||
ResponseHeaders http.Header
|
||||
Request *http.Request
|
||||
}
|
||||
}
|
||||
|
||||
// logWriter is used to capture status for log messages
|
||||
type logWriter struct {
|
||||
responseWriter http.ResponseWriter
|
||||
logMessage *logMessage
|
||||
}
|
||||
|
||||
// WriteHeader writes headers and stores status in LogMessage
|
||||
func (w *logWriter) WriteHeader(status int) {
|
||||
w.logMessage.StatusMessage = http.StatusText(status)
|
||||
w.responseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
// Header Dummy wrapper for LogWriter
|
||||
func (w *logWriter) Header() http.Header {
|
||||
return w.responseWriter.Header()
|
||||
}
|
||||
|
||||
// Write Dummy wrapper for LogWriter
|
||||
func (w *logWriter) Write(data []byte) (int, error) {
|
||||
return w.responseWriter.Write(data)
|
||||
}
|
||||
|
||||
func (h *logHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
logMessage := &logMessage{
|
||||
StartTime: time.Now().UTC(),
|
||||
}
|
||||
logWriter := &logWriter{responseWriter: w, logMessage: logMessage}
|
||||
h.handler.ServeHTTP(logWriter, req)
|
||||
h.logger <- getLogMessage(logMessage, w, req)
|
||||
}
|
||||
|
||||
func getLogMessage(logMessage *logMessage, w http.ResponseWriter, req *http.Request) []byte {
|
||||
// store lower level details
|
||||
logMessage.HTTP.ResponseHeaders = w.Header()
|
||||
logMessage.HTTP.Request = req
|
||||
|
||||
logMessage.Duration = time.Now().UTC().Sub(logMessage.StartTime)
|
||||
js, _ := json.Marshal(logMessage)
|
||||
js = append(js, byte('\n')) // append a new line
|
||||
return js
|
||||
}
|
||||
|
||||
// LoggingHandler logs requests
|
||||
func LoggingHandler(h http.Handler) http.Handler {
|
||||
logger, _ := fileLogger("access.log")
|
||||
return &logHandler{handler: h, logger: logger}
|
||||
}
|
||||
|
||||
// fileLogger returns a channel that is used to write to the logger
|
||||
func fileLogger(filename string) (chan<- []byte, error) {
|
||||
ch := make(chan []byte)
|
||||
file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
for message := range ch {
|
||||
if _, err := io.Copy(file, bytes.NewBuffer(message)); err != nil {
|
||||
// log.Errorln(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch, nil
|
||||
}
|
||||
@@ -1,609 +0,0 @@
|
||||
/*
|
||||
* Minio 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 implieapi.Donut.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
const (
|
||||
maxPartsList = 1000
|
||||
)
|
||||
|
||||
// GetObjectHandler - GET Object
|
||||
// ----------
|
||||
// This implementation of the GET operation retrieves object. To use GET,
|
||||
// you must have READ access to the object.
|
||||
func (api Minio) GetObjectHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
if !api.isValidOp(w, req, acceptsContentType) {
|
||||
return
|
||||
}
|
||||
|
||||
var object, bucket string
|
||||
vars := mux.Vars(req)
|
||||
bucket = vars["bucket"]
|
||||
object = vars["object"]
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
metadata, err := api.Donut.GetObjectMetadata(bucket, object, signature)
|
||||
if err == nil {
|
||||
var hrange *httpRange
|
||||
hrange, err = getRequestedRange(req.Header.Get("Range"), metadata.Size)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InvalidRange, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
setObjectHeaders(w, metadata, hrange)
|
||||
if _, err = api.Donut.GetObject(w, bucket, object, hrange.start, hrange.length); err != nil {
|
||||
// unable to write headers, we've already printed data. Just close the connection.
|
||||
// log.Error.Println(err.Trace())
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNameInvalid:
|
||||
writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNotFound:
|
||||
writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path)
|
||||
case donut.ObjectNotFound:
|
||||
writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path)
|
||||
case donut.ObjectNameInvalid:
|
||||
writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// HeadObjectHandler - HEAD Object
|
||||
// -----------
|
||||
// The HEAD operation retrieves metadata from an object without returning the object itself.
|
||||
func (api Minio) HeadObjectHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
if !api.isValidOp(w, req, acceptsContentType) {
|
||||
return
|
||||
}
|
||||
|
||||
var object, bucket string
|
||||
vars := mux.Vars(req)
|
||||
bucket = vars["bucket"]
|
||||
object = vars["object"]
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
metadata, err := api.Donut.GetObjectMetadata(bucket, object, signature)
|
||||
if err == nil {
|
||||
setObjectHeaders(w, metadata, nil)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNameInvalid:
|
||||
writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNotFound:
|
||||
writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path)
|
||||
case donut.ObjectNotFound:
|
||||
writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path)
|
||||
case donut.ObjectNameInvalid:
|
||||
writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// PutObjectHandler - PUT Object
|
||||
// ----------
|
||||
// This implementation of the PUT operation adds an object to a bucket.
|
||||
func (api Minio) PutObjectHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
if !api.isValidOp(w, req, acceptsContentType) {
|
||||
return
|
||||
}
|
||||
|
||||
var object, bucket string
|
||||
vars := mux.Vars(req)
|
||||
bucket = vars["bucket"]
|
||||
object = vars["object"]
|
||||
|
||||
// get Content-MD5 sent by client and verify if valid
|
||||
md5 := req.Header.Get("Content-MD5")
|
||||
if !isValidMD5(md5) {
|
||||
writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
/// if Content-Length missing, deny the request
|
||||
size := req.Header.Get("Content-Length")
|
||||
if size == "" {
|
||||
writeErrorResponse(w, req, MissingContentLength, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
/// maximum Upload size for objects in a single operation
|
||||
if isMaxObjectSize(size) {
|
||||
writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
/// minimum Upload size for objects in a single operation
|
||||
//
|
||||
// Surprisingly while Amazon in their document states that S3 objects have 1byte
|
||||
// as the minimum limit, they do not seem to enforce it one can successfully
|
||||
// create a 0byte file using a regular putObject() operation
|
||||
//
|
||||
// if isMinObjectSize(size) {
|
||||
// writeErrorResponse(w, req, EntityTooSmall, acceptsContentType, req.URL.Path)
|
||||
// return
|
||||
// }
|
||||
var sizeInt64 int64
|
||||
{
|
||||
var err error
|
||||
sizeInt64, err = strconv.ParseInt(size, 10, 64)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InvalidRequest, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
metadata, err := api.Donut.CreateObject(bucket, object, md5, sizeInt64, req.Body, nil, signature)
|
||||
if err == nil {
|
||||
w.Header().Set("ETag", metadata.MD5Sum)
|
||||
writeSuccessResponse(w, acceptsContentType)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.BucketNotFound:
|
||||
writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path)
|
||||
case donut.BucketNameInvalid:
|
||||
writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path)
|
||||
case donut.ObjectExists:
|
||||
writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path)
|
||||
case donut.BadDigest:
|
||||
writeErrorResponse(w, req, BadDigest, acceptsContentType, req.URL.Path)
|
||||
case donut.MissingDateHeader:
|
||||
writeErrorResponse(w, req, RequestTimeTooSkewed, acceptsContentType, req.URL.Path)
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.IncompleteBody:
|
||||
writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path)
|
||||
case donut.EntityTooLarge:
|
||||
writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path)
|
||||
case donut.InvalidDigest:
|
||||
writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Multipart API
|
||||
|
||||
// NewMultipartUploadHandler - New multipart upload
|
||||
func (api Minio) NewMultipartUploadHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
if !api.isValidOp(w, req, acceptsContentType) {
|
||||
return
|
||||
}
|
||||
|
||||
if !isRequestUploads(req.URL.Query()) {
|
||||
writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
var object, bucket string
|
||||
vars := mux.Vars(req)
|
||||
bucket = vars["bucket"]
|
||||
object = vars["object"]
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
uploadID, err := api.Donut.NewMultipartUpload(bucket, object, req.Header.Get("Content-Type"), signature)
|
||||
if err == nil {
|
||||
response := generateInitiateMultipartUploadResponse(bucket, object, uploadID)
|
||||
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
||||
// write headers
|
||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
||||
// write body
|
||||
w.Write(encodedSuccessResponse)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.ObjectExists:
|
||||
writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// PutObjectPartHandler - Upload part
|
||||
func (api Minio) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
if !api.isValidOp(w, req, acceptsContentType) {
|
||||
return
|
||||
}
|
||||
|
||||
// get Content-MD5 sent by client and verify if valid
|
||||
md5 := req.Header.Get("Content-MD5")
|
||||
if !isValidMD5(md5) {
|
||||
writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
/// if Content-Length missing, throw away
|
||||
size := req.Header.Get("Content-Length")
|
||||
if size == "" {
|
||||
writeErrorResponse(w, req, MissingContentLength, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
/// maximum Upload size for multipart objects in a single operation
|
||||
if isMaxObjectSize(size) {
|
||||
writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
var sizeInt64 int64
|
||||
{
|
||||
var err error
|
||||
sizeInt64, err = strconv.ParseInt(size, 10, 64)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InvalidRequest, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
bucket := vars["bucket"]
|
||||
object := vars["object"]
|
||||
|
||||
uploadID := req.URL.Query().Get("uploadId")
|
||||
partIDString := req.URL.Query().Get("partNumber")
|
||||
|
||||
var partID int
|
||||
{
|
||||
var err error
|
||||
partID, err = strconv.Atoi(partIDString)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InvalidPart, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
calculatedMD5, err := api.Donut.CreateObjectPart(bucket, object, uploadID, partID, "", md5, sizeInt64, req.Body, signature)
|
||||
if err == nil {
|
||||
w.Header().Set("ETag", calculatedMD5)
|
||||
writeSuccessResponse(w, acceptsContentType)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.InvalidUploadID:
|
||||
writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path)
|
||||
case donut.ObjectExists:
|
||||
writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path)
|
||||
case donut.BadDigest:
|
||||
writeErrorResponse(w, req, BadDigest, acceptsContentType, req.URL.Path)
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.IncompleteBody:
|
||||
writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path)
|
||||
case donut.EntityTooLarge:
|
||||
writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path)
|
||||
case donut.InvalidDigest:
|
||||
writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// AbortMultipartUploadHandler - Abort multipart upload
|
||||
func (api Minio) AbortMultipartUploadHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
if !api.isValidOp(w, req, acceptsContentType) {
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
bucket := vars["bucket"]
|
||||
object := vars["object"]
|
||||
|
||||
objectResourcesMetadata := getObjectResources(req.URL.Query())
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err := api.Donut.AbortMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, signature)
|
||||
if err == nil {
|
||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), 0)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.InvalidUploadID:
|
||||
writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// ListObjectPartsHandler - List object parts
|
||||
func (api Minio) ListObjectPartsHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
if !api.isValidOp(w, req, acceptsContentType) {
|
||||
return
|
||||
}
|
||||
|
||||
objectResourcesMetadata := getObjectResources(req.URL.Query())
|
||||
if objectResourcesMetadata.PartNumberMarker < 0 {
|
||||
writeErrorResponse(w, req, InvalidPartNumberMarker, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
if objectResourcesMetadata.MaxParts < 0 {
|
||||
writeErrorResponse(w, req, InvalidMaxParts, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
if objectResourcesMetadata.MaxParts == 0 {
|
||||
objectResourcesMetadata.MaxParts = maxPartsList
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
bucket := vars["bucket"]
|
||||
object := vars["object"]
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
objectResourcesMetadata, err := api.Donut.ListObjectParts(bucket, object, objectResourcesMetadata, signature)
|
||||
if err == nil {
|
||||
response := generateListPartsResponse(objectResourcesMetadata)
|
||||
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
||||
// write headers
|
||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
||||
// write body
|
||||
w.Write(encodedSuccessResponse)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.InvalidUploadID:
|
||||
writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// CompleteMultipartUploadHandler - Complete multipart upload
|
||||
func (api Minio) CompleteMultipartUploadHandler(w http.ResponseWriter, req *http.Request) {
|
||||
// Ticket master block
|
||||
{
|
||||
op := Operation{}
|
||||
op.ProceedCh = make(chan struct{})
|
||||
api.OP <- op
|
||||
// block until Ticket master gives us a go
|
||||
<-op.ProceedCh
|
||||
}
|
||||
|
||||
acceptsContentType := getContentType(req)
|
||||
if !api.isValidOp(w, req, acceptsContentType) {
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
bucket := vars["bucket"]
|
||||
object := vars["object"]
|
||||
|
||||
objectResourcesMetadata := getObjectResources(req.URL.Query())
|
||||
|
||||
var signature *donut.Signature
|
||||
if _, ok := req.Header["Authorization"]; ok {
|
||||
// Init signature V4 verification
|
||||
var err *probe.Error
|
||||
signature, err = initSignatureV4(req)
|
||||
if err != nil {
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
metadata, err := api.Donut.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, req.Body, signature)
|
||||
if err == nil {
|
||||
response := generateCompleteMultpartUploadResponse(bucket, object, "", metadata.MD5Sum)
|
||||
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
|
||||
// write headers
|
||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse))
|
||||
// write body
|
||||
w.Write(encodedSuccessResponse)
|
||||
return
|
||||
}
|
||||
switch err.ToGoError().(type) {
|
||||
case donut.InvalidUploadID:
|
||||
writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path)
|
||||
case donut.InvalidPart:
|
||||
writeErrorResponse(w, req, InvalidPart, acceptsContentType, req.URL.Path)
|
||||
case donut.InvalidPartOrder:
|
||||
writeErrorResponse(w, req, InvalidPartOrder, acceptsContentType, req.URL.Path)
|
||||
case donut.MissingDateHeader:
|
||||
writeErrorResponse(w, req, RequestTimeTooSkewed, acceptsContentType, req.URL.Path)
|
||||
case donut.SignatureDoesNotMatch:
|
||||
writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path)
|
||||
case donut.IncompleteBody:
|
||||
writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path)
|
||||
case donut.MalformedXML:
|
||||
writeErrorResponse(w, req, MalformedXML, acceptsContentType, req.URL.Path)
|
||||
default:
|
||||
// log.Error.Println(err.Trace())
|
||||
writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete API
|
||||
|
||||
// DeleteBucketHandler - Delete bucket
|
||||
func (api Minio) DeleteBucketHandler(w http.ResponseWriter, req *http.Request) {
|
||||
error := getErrorCode(MethodNotAllowed)
|
||||
w.WriteHeader(error.HTTPStatusCode)
|
||||
}
|
||||
|
||||
// DeleteObjectHandler - Delete object
|
||||
func (api Minio) DeleteObjectHandler(w http.ResponseWriter, req *http.Request) {
|
||||
error := getErrorCode(MethodNotAllowed)
|
||||
w.WriteHeader(error.HTTPStatusCode)
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
const (
|
||||
b = "bytes="
|
||||
)
|
||||
|
||||
// HttpRange specifies the byte range to be sent to the client.
|
||||
type httpRange struct {
|
||||
start, length, size int64
|
||||
}
|
||||
|
||||
// String populate range stringer interface
|
||||
func (r *httpRange) String() string {
|
||||
return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, r.size)
|
||||
}
|
||||
|
||||
// Grab new range from request header
|
||||
func getRequestedRange(hrange string, size int64) (*httpRange, *probe.Error) {
|
||||
r := &httpRange{
|
||||
start: 0,
|
||||
length: 0,
|
||||
size: 0,
|
||||
}
|
||||
r.size = size
|
||||
if hrange != "" {
|
||||
err := r.parseRange(hrange)
|
||||
if err != nil {
|
||||
return nil, err.Trace()
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *httpRange) parse(ra string) *probe.Error {
|
||||
i := strings.Index(ra, "-")
|
||||
if i < 0 {
|
||||
return probe.NewError(donut.InvalidRange{})
|
||||
}
|
||||
start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
|
||||
if start == "" {
|
||||
// If no start is specified, end specifies the
|
||||
// range start relative to the end of the file.
|
||||
i, err := strconv.ParseInt(end, 10, 64)
|
||||
if err != nil {
|
||||
return probe.NewError(donut.InvalidRange{})
|
||||
}
|
||||
if i > r.size {
|
||||
i = r.size
|
||||
}
|
||||
r.start = r.size - i
|
||||
r.length = r.size - r.start
|
||||
} else {
|
||||
i, err := strconv.ParseInt(start, 10, 64)
|
||||
if err != nil || i > r.size || i < 0 {
|
||||
return probe.NewError(donut.InvalidRange{})
|
||||
}
|
||||
r.start = i
|
||||
if end == "" {
|
||||
// If no end is specified, range extends to end of the file.
|
||||
r.length = r.size - r.start
|
||||
} else {
|
||||
i, err := strconv.ParseInt(end, 10, 64)
|
||||
if err != nil || r.start > i {
|
||||
return probe.NewError(donut.InvalidRange{})
|
||||
}
|
||||
if i >= r.size {
|
||||
i = r.size - 1
|
||||
}
|
||||
r.length = i - r.start + 1
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseRange parses a Range header string as per RFC 2616.
|
||||
func (r *httpRange) parseRange(s string) *probe.Error {
|
||||
if s == "" {
|
||||
return probe.NewError(errors.New("header not present"))
|
||||
}
|
||||
if !strings.HasPrefix(s, b) {
|
||||
return probe.NewError(donut.InvalidRange{})
|
||||
}
|
||||
|
||||
ras := strings.Split(s[len(b):], ",")
|
||||
if len(ras) == 0 {
|
||||
return probe.NewError(errors.New("invalid request"))
|
||||
}
|
||||
// Just pick the first one and ignore the rest, we only support one range per object
|
||||
if len(ras) > 1 {
|
||||
return probe.NewError(errors.New("multiple ranges specified"))
|
||||
}
|
||||
|
||||
ra := strings.TrimSpace(ras[0])
|
||||
if ra == "" {
|
||||
return probe.NewError(donut.InvalidRange{})
|
||||
}
|
||||
return r.parse(ra)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
)
|
||||
|
||||
// parse bucket url queries
|
||||
func getBucketResources(values url.Values) (v donut.BucketResourcesMetadata) {
|
||||
v.Prefix = values.Get("prefix")
|
||||
v.Marker = values.Get("marker")
|
||||
v.Maxkeys, _ = strconv.Atoi(values.Get("max-keys"))
|
||||
v.Delimiter = values.Get("delimiter")
|
||||
v.EncodingType = values.Get("encoding-type")
|
||||
return
|
||||
}
|
||||
|
||||
// part bucket url queries for ?uploads
|
||||
func getBucketMultipartResources(values url.Values) (v donut.BucketMultipartResourcesMetadata) {
|
||||
v.Prefix = values.Get("prefix")
|
||||
v.KeyMarker = values.Get("key-marker")
|
||||
v.MaxUploads, _ = strconv.Atoi(values.Get("max-uploads"))
|
||||
v.Delimiter = values.Get("delimiter")
|
||||
v.EncodingType = values.Get("encoding-type")
|
||||
v.UploadIDMarker = values.Get("upload-id-marker")
|
||||
return
|
||||
}
|
||||
|
||||
// parse object url queries
|
||||
func getObjectResources(values url.Values) (v donut.ObjectResourcesMetadata) {
|
||||
v.UploadID = values.Get("uploadId")
|
||||
v.PartNumberMarker, _ = strconv.Atoi(values.Get("part-number-marker"))
|
||||
v.MaxParts, _ = strconv.Atoi(values.Get("max-parts"))
|
||||
v.EncodingType = values.Get("encoding-type")
|
||||
return
|
||||
}
|
||||
|
||||
// check if req quere values carry uploads resource
|
||||
func isRequestUploads(values url.Values) bool {
|
||||
_, ok := values["uploads"]
|
||||
return ok
|
||||
}
|
||||
|
||||
// check if req query values carry acl resource
|
||||
func isRequestBucketACL(values url.Values) bool {
|
||||
_, ok := values["acl"]
|
||||
return ok
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
)
|
||||
|
||||
// Reply date format
|
||||
const (
|
||||
rfcFormat = "2006-01-02T15:04:05.000Z"
|
||||
)
|
||||
|
||||
// takes an array of Bucketmetadata information for serialization
|
||||
// input:
|
||||
// array of bucket metadata
|
||||
//
|
||||
// output:
|
||||
// populated struct that can be serialized to match xml and json api spec output
|
||||
func generateListBucketsResponse(buckets []donut.BucketMetadata) ListBucketsResponse {
|
||||
var listbuckets []*Bucket
|
||||
var data = ListBucketsResponse{}
|
||||
var owner = Owner{}
|
||||
|
||||
owner.ID = "minio"
|
||||
owner.DisplayName = "minio"
|
||||
|
||||
for _, bucket := range buckets {
|
||||
var listbucket = &Bucket{}
|
||||
listbucket.Name = bucket.Name
|
||||
listbucket.CreationDate = bucket.Created.Format(rfcFormat)
|
||||
listbuckets = append(listbuckets, listbucket)
|
||||
}
|
||||
|
||||
data.Owner = owner
|
||||
data.Buckets.Bucket = listbuckets
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// itemKey
|
||||
type itemKey []*Object
|
||||
|
||||
func (b itemKey) Len() int { return len(b) }
|
||||
func (b itemKey) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
func (b itemKey) Less(i, j int) bool { return b[i].Key < b[j].Key }
|
||||
|
||||
// takes a set of objects and prepares the objects for serialization
|
||||
// input:
|
||||
// bucket name
|
||||
// array of object metadata
|
||||
// results truncated flag
|
||||
//
|
||||
// output:
|
||||
// populated struct that can be serialized to match xml and json api spec output
|
||||
func generateListObjectsResponse(bucket string, objects []donut.ObjectMetadata, bucketResources donut.BucketResourcesMetadata) ListObjectsResponse {
|
||||
var contents []*Object
|
||||
var prefixes []*CommonPrefix
|
||||
var owner = Owner{}
|
||||
var data = ListObjectsResponse{}
|
||||
|
||||
owner.ID = "minio"
|
||||
owner.DisplayName = "minio"
|
||||
|
||||
for _, object := range objects {
|
||||
var content = &Object{}
|
||||
if object.Object == "" {
|
||||
continue
|
||||
}
|
||||
content.Key = object.Object
|
||||
content.LastModified = object.Created.Format(rfcFormat)
|
||||
content.ETag = "\"" + object.MD5Sum + "\""
|
||||
content.Size = object.Size
|
||||
content.StorageClass = "STANDARD"
|
||||
content.Owner = owner
|
||||
contents = append(contents, content)
|
||||
}
|
||||
sort.Sort(itemKey(contents))
|
||||
// TODO - support EncodingType in xml decoding
|
||||
data.Name = bucket
|
||||
data.Contents = contents
|
||||
data.MaxKeys = bucketResources.Maxkeys
|
||||
data.Prefix = bucketResources.Prefix
|
||||
data.Delimiter = bucketResources.Delimiter
|
||||
data.Marker = bucketResources.Marker
|
||||
data.NextMarker = bucketResources.NextMarker
|
||||
data.IsTruncated = bucketResources.IsTruncated
|
||||
for _, prefix := range bucketResources.CommonPrefixes {
|
||||
var prefixItem = &CommonPrefix{}
|
||||
prefixItem.Prefix = prefix
|
||||
prefixes = append(prefixes, prefixItem)
|
||||
}
|
||||
data.CommonPrefixes = prefixes
|
||||
return data
|
||||
}
|
||||
|
||||
// generateInitiateMultipartUploadResponse
|
||||
func generateInitiateMultipartUploadResponse(bucket, key, uploadID string) InitiateMultipartUploadResponse {
|
||||
return InitiateMultipartUploadResponse{
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
UploadID: uploadID,
|
||||
}
|
||||
}
|
||||
|
||||
// generateCompleteMultipartUploadResponse
|
||||
func generateCompleteMultpartUploadResponse(bucket, key, location, etag string) CompleteMultipartUploadResponse {
|
||||
return CompleteMultipartUploadResponse{
|
||||
Location: location,
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
ETag: etag,
|
||||
}
|
||||
}
|
||||
|
||||
// generateListPartsResult
|
||||
func generateListPartsResponse(objectMetadata donut.ObjectResourcesMetadata) ListPartsResponse {
|
||||
// TODO - support EncodingType in xml decoding
|
||||
listPartsResponse := ListPartsResponse{}
|
||||
listPartsResponse.Bucket = objectMetadata.Bucket
|
||||
listPartsResponse.Key = objectMetadata.Key
|
||||
listPartsResponse.UploadID = objectMetadata.UploadID
|
||||
listPartsResponse.StorageClass = "STANDARD"
|
||||
listPartsResponse.Initiator.ID = "minio"
|
||||
listPartsResponse.Initiator.DisplayName = "minio"
|
||||
listPartsResponse.Owner.ID = "minio"
|
||||
listPartsResponse.Owner.DisplayName = "minio"
|
||||
|
||||
listPartsResponse.MaxParts = objectMetadata.MaxParts
|
||||
listPartsResponse.PartNumberMarker = objectMetadata.PartNumberMarker
|
||||
listPartsResponse.IsTruncated = objectMetadata.IsTruncated
|
||||
listPartsResponse.NextPartNumberMarker = objectMetadata.NextPartNumberMarker
|
||||
|
||||
listPartsResponse.Part = make([]*Part, len(objectMetadata.Part))
|
||||
for _, part := range objectMetadata.Part {
|
||||
newPart := &Part{}
|
||||
newPart.PartNumber = part.PartNumber
|
||||
newPart.ETag = "\"" + part.ETag + "\""
|
||||
newPart.Size = part.Size
|
||||
newPart.LastModified = part.LastModified.Format(rfcFormat)
|
||||
listPartsResponse.Part = append(listPartsResponse.Part, newPart)
|
||||
}
|
||||
return listPartsResponse
|
||||
}
|
||||
|
||||
// generateListMultipartUploadsResponse
|
||||
func generateListMultipartUploadsResponse(bucket string, metadata donut.BucketMultipartResourcesMetadata) ListMultipartUploadsResponse {
|
||||
listMultipartUploadsResponse := ListMultipartUploadsResponse{}
|
||||
listMultipartUploadsResponse.Bucket = bucket
|
||||
listMultipartUploadsResponse.Delimiter = metadata.Delimiter
|
||||
listMultipartUploadsResponse.IsTruncated = metadata.IsTruncated
|
||||
listMultipartUploadsResponse.EncodingType = metadata.EncodingType
|
||||
listMultipartUploadsResponse.Prefix = metadata.Prefix
|
||||
listMultipartUploadsResponse.KeyMarker = metadata.KeyMarker
|
||||
listMultipartUploadsResponse.NextKeyMarker = metadata.NextKeyMarker
|
||||
listMultipartUploadsResponse.MaxUploads = metadata.MaxUploads
|
||||
listMultipartUploadsResponse.NextUploadIDMarker = metadata.NextUploadIDMarker
|
||||
listMultipartUploadsResponse.UploadIDMarker = metadata.UploadIDMarker
|
||||
|
||||
listMultipartUploadsResponse.Upload = make([]*Upload, len(metadata.Upload))
|
||||
for _, upload := range metadata.Upload {
|
||||
newUpload := &Upload{}
|
||||
newUpload.UploadID = upload.UploadID
|
||||
newUpload.Key = upload.Key
|
||||
newUpload.Initiated = upload.Initiated.Format(rfcFormat)
|
||||
listMultipartUploadsResponse.Upload = append(listMultipartUploadsResponse.Upload, newUpload)
|
||||
}
|
||||
return listMultipartUploadsResponse
|
||||
}
|
||||
|
||||
// writeSuccessResponse write success headers
|
||||
func writeSuccessResponse(w http.ResponseWriter, acceptsContentType contentType) {
|
||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), 0)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// writeErrorRespone write error headers
|
||||
func writeErrorResponse(w http.ResponseWriter, req *http.Request, errorType int, acceptsContentType contentType, resource string) {
|
||||
error := getErrorCode(errorType)
|
||||
// generate error response
|
||||
errorResponse := getErrorResponse(error, resource)
|
||||
encodedErrorResponse := encodeErrorResponse(errorResponse, acceptsContentType)
|
||||
// set common headers
|
||||
setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedErrorResponse))
|
||||
// write Header
|
||||
w.WriteHeader(error.HTTPStatusCode)
|
||||
// HEAD should have no body
|
||||
if req.Method != "HEAD" {
|
||||
// write error body
|
||||
w.Write(encodedErrorResponse)
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
const (
|
||||
authHeaderPrefix = "AWS4-HMAC-SHA256"
|
||||
)
|
||||
|
||||
// getCredentialsFromAuth parse credentials tag from authorization value
|
||||
func getCredentialsFromAuth(authValue string) ([]string, *probe.Error) {
|
||||
if authValue == "" {
|
||||
return nil, probe.NewError(errMissingAuthHeaderValue)
|
||||
}
|
||||
authFields := strings.Split(strings.TrimSpace(authValue), ",")
|
||||
if len(authFields) != 3 {
|
||||
return nil, probe.NewError(errInvalidAuthHeaderValue)
|
||||
}
|
||||
authPrefixFields := strings.Fields(authFields[0])
|
||||
if len(authPrefixFields) != 2 {
|
||||
return nil, probe.NewError(errMissingFieldsAuthHeader)
|
||||
}
|
||||
if authPrefixFields[0] != authHeaderPrefix {
|
||||
return nil, probe.NewError(errInvalidAuthHeaderPrefix)
|
||||
}
|
||||
credentials := strings.Split(strings.TrimSpace(authPrefixFields[1]), "=")
|
||||
if len(credentials) != 2 {
|
||||
return nil, probe.NewError(errMissingFieldsCredentialTag)
|
||||
}
|
||||
if len(strings.Split(strings.TrimSpace(authFields[1]), "=")) != 2 {
|
||||
return nil, probe.NewError(errMissingFieldsSignedHeadersTag)
|
||||
}
|
||||
if len(strings.Split(strings.TrimSpace(authFields[2]), "=")) != 2 {
|
||||
return nil, probe.NewError(errMissingFieldsSignatureTag)
|
||||
}
|
||||
credentialElements := strings.Split(strings.TrimSpace(credentials[1]), "/")
|
||||
if len(credentialElements) != 5 {
|
||||
return nil, probe.NewError(errCredentialTagMalformed)
|
||||
}
|
||||
return credentialElements, nil
|
||||
}
|
||||
|
||||
// verify if authHeader value has valid region
|
||||
func isValidRegion(authHeaderValue string) *probe.Error {
|
||||
credentialElements, err := getCredentialsFromAuth(authHeaderValue)
|
||||
if err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
region := credentialElements[2]
|
||||
if region != "milkyway" {
|
||||
return probe.NewError(errInvalidRegion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// stripAccessKeyID - strip only access key id from auth header
|
||||
func stripAccessKeyID(authHeaderValue string) (string, *probe.Error) {
|
||||
if err := isValidRegion(authHeaderValue); err != nil {
|
||||
return "", err.Trace()
|
||||
}
|
||||
credentialElements, err := getCredentialsFromAuth(authHeaderValue)
|
||||
if err != nil {
|
||||
return "", err.Trace()
|
||||
}
|
||||
accessKeyID := credentialElements[0]
|
||||
if !auth.IsValidAccessKey(accessKeyID) {
|
||||
return "", probe.NewError(errAccessKeyIDInvalid)
|
||||
}
|
||||
return accessKeyID, nil
|
||||
}
|
||||
|
||||
// initSignatureV4 initializing signature verification
|
||||
func initSignatureV4(req *http.Request) (*donut.Signature, *probe.Error) {
|
||||
// strip auth from authorization header
|
||||
authHeaderValue := req.Header.Get("Authorization")
|
||||
accessKeyID, err := stripAccessKeyID(authHeaderValue)
|
||||
if err != nil {
|
||||
return nil, err.Trace()
|
||||
}
|
||||
authConfig, err := auth.LoadConfig()
|
||||
if err != nil {
|
||||
return nil, err.Trace()
|
||||
}
|
||||
for _, user := range authConfig.Users {
|
||||
if user.AccessKeyID == accessKeyID {
|
||||
signature := &donut.Signature{
|
||||
AccessKeyID: user.AccessKeyID,
|
||||
SecretAccessKey: user.SecretAccessKey,
|
||||
AuthHeader: authHeaderValue,
|
||||
Request: req,
|
||||
}
|
||||
return signature, nil
|
||||
}
|
||||
}
|
||||
return nil, probe.NewError(errors.New("AccessKeyID not found"))
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import "errors"
|
||||
|
||||
// errMissingAuthHeader means that Authorization header
|
||||
// has missing value or it is empty
|
||||
var errMissingAuthHeaderValue = errors.New("Missing auth header value")
|
||||
|
||||
// errInvalidAuthHeaderValue means that Authorization
|
||||
// header is available but is malformed and not in
|
||||
// accordance with signature v4
|
||||
var errInvalidAuthHeaderValue = errors.New("Invalid auth header value")
|
||||
|
||||
// errInvalidAuthHeaderPrefix means that Authorization header
|
||||
// has a wrong prefix only supported value should be "AWS4-HMAC-SHA256"
|
||||
var errInvalidAuthHeaderPrefix = errors.New("Invalid auth header prefix")
|
||||
|
||||
// errMissingFieldsAuthHeader means that Authorization
|
||||
// header is available but has some missing fields
|
||||
var errMissingFieldsAuthHeader = errors.New("Missing fields in auth header")
|
||||
|
||||
// errMissingFieldsCredentialTag means that Authorization
|
||||
// header credentials tag has some missing fields
|
||||
var errMissingFieldsCredentialTag = errors.New("Missing fields in crendential tag")
|
||||
|
||||
// errMissingFieldsSignedHeadersTag means that Authorization
|
||||
// header signed headers tag has some missing fields
|
||||
var errMissingFieldsSignedHeadersTag = errors.New("Missing fields in signed headers tag")
|
||||
|
||||
// errMissingFieldsSignatureTag means that Authorization
|
||||
// header signature tag has missing fields
|
||||
var errMissingFieldsSignatureTag = errors.New("Missing fields in signature tag")
|
||||
|
||||
// errCredentialTagMalformed means that Authorization header
|
||||
// credential tag is malformed
|
||||
var errCredentialTagMalformed = errors.New("Invalid credential tag malformed")
|
||||
|
||||
// errInvalidRegion means that the region element from credential tag
|
||||
// in Authorization header is invalid
|
||||
var errInvalidRegion = errors.New("Invalid region")
|
||||
|
||||
// errAccessKeyIDInvalid means that the accessKeyID element from
|
||||
// credential tag in Authorization header is invalid
|
||||
var errAccessKeyIDInvalid = errors.New("AccessKeyID invalid")
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Minio 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 api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isValidMD5 - verify if valid md5
|
||||
func isValidMD5(md5 string) bool {
|
||||
if md5 == "" {
|
||||
return true
|
||||
}
|
||||
_, err := base64.StdEncoding.DecodeString(strings.TrimSpace(md5))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/// http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html
|
||||
const (
|
||||
// maximum object size per PUT request is 5GB
|
||||
maxObjectSize = 1024 * 1024 * 1024 * 5
|
||||
// mimimum object size per Multipart PUT request is 5MB
|
||||
minMultiPartObjectSize = 1024 * 1024 * 5
|
||||
// minimum object size per PUT request is 1B
|
||||
minObjectSize = 1
|
||||
)
|
||||
|
||||
// isMaxObjectSize - verify if max object size
|
||||
func isMaxObjectSize(size string) bool {
|
||||
i, err := strconv.ParseInt(size, 10, 64)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if i > maxObjectSize {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isMinObjectSize - verify if min object size
|
||||
func isMinObjectSize(size string) bool {
|
||||
i, err := strconv.ParseInt(size, 10, 64)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if i < minObjectSize {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isMinMultipartObjectSize - verify if the uploaded multipart is of minimum size
|
||||
func isMinMultipartObjectSize(size string) bool {
|
||||
i, err := strconv.ParseInt(size, 10, 64)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if i < minMultiPartObjectSize {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,897 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2014 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 server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
"github.com/minio/minio/pkg/server/api"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type MyAPIDonutCacheSuite struct {
|
||||
root string
|
||||
}
|
||||
|
||||
var _ = Suite(&MyAPIDonutCacheSuite{})
|
||||
|
||||
var testAPIDonutCacheServer *httptest.Server
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) SetUpSuite(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "api-")
|
||||
c.Assert(err, IsNil)
|
||||
s.root = root
|
||||
|
||||
conf := &donut.Config{}
|
||||
conf.Version = "0.0.1"
|
||||
conf.MaxSize = 100000
|
||||
donut.SetDonutConfigPath(filepath.Join(root, "donut.json"))
|
||||
perr := donut.SaveConfig(conf)
|
||||
c.Assert(perr, IsNil)
|
||||
|
||||
httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16})
|
||||
go startTM(minioAPI)
|
||||
testAPIDonutCacheServer = httptest.NewServer(httpHandler)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TearDownSuite(c *C) {
|
||||
os.RemoveAll(s.root)
|
||||
testAPIDonutCacheServer.Close()
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestDeleteBucket(c *C) {
|
||||
request, err := http.NewRequest("DELETE", testAPIDonutCacheServer.URL+"/mybucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := &http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestDeleteObject(c *C) {
|
||||
request, err := http.NewRequest("DELETE", testAPIDonutCacheServer.URL+"/mybucket/myobject", nil)
|
||||
c.Assert(err, IsNil)
|
||||
client := &http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestNonExistantBucket(c *C) {
|
||||
request, err := http.NewRequest("HEAD", testAPIDonutCacheServer.URL+"/nonexistantbucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestEmptyObject(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/emptyobject", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/emptyobject/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/emptyobject/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, buffer.Bytes()))
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestBucket(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/bucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("HEAD", testAPIDonutCacheServer.URL+"/bucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestObject(c *C) {
|
||||
buffer := bytes.NewBufferString("hello world")
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/testobject", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/testobject/object", buffer)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/testobject/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(responseBody, DeepEquals, []byte("hello world"))
|
||||
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestMultipleObjects(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/multipleobjects", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/multipleobjects/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound)
|
||||
|
||||
//// test object 1
|
||||
|
||||
// get object
|
||||
buffer1 := bytes.NewBufferString("hello one")
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/multipleobjects/object1", buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/multipleobjects/object1", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// verify response data
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello one")))
|
||||
|
||||
buffer2 := bytes.NewBufferString("hello two")
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/multipleobjects/object2", buffer2)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/multipleobjects/object2", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// verify response data
|
||||
responseBody, err = ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello two")))
|
||||
|
||||
buffer3 := bytes.NewBufferString("hello three")
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/multipleobjects/object3", buffer3)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/multipleobjects/object3", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// verify object
|
||||
responseBody, err = ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello three")))
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestNotImplemented(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutCacheServer.URL+"/bucket/object?policy", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusNotImplemented)
|
||||
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestHeader(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutCacheServer.URL+"/bucket/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestPutBucket(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/put-bucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestPutObject(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/put-object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/put-object/object", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestListBuckets(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutCacheServer.URL+"/", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var results api.ListBucketsResponse
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
err = decoder.Decode(&results)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestNotBeAbleToCreateObjectInNonexistantBucket(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/innonexistantbucket/object", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist.", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestHeadOnObject(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/headonobject", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/headonobject/object1", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("HEAD", testAPIDonutCacheServer.URL+"/headonobject/object1", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestHeadOnBucket(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/headonbucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("HEAD", testAPIDonutCacheServer.URL+"/headonbucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
/* Enable when we have full working signature v4
|
||||
func (s *MyAPIDonutCacheSuite) TestDateFormat(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/dateformat", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
// set an invalid date
|
||||
request.Header.Set("Date", "asfasdfadf")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "RequestTimeTooSkewed",
|
||||
"The difference between the request time and the server's time is too large.", http.StatusForbidden)
|
||||
|
||||
request.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
*/
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestXMLNameNotInBucketListJson(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutCacheServer.URL+"/", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
byteResults, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(strings.Contains(string(byteResults), "XML"), Equals, false)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestXMLNameNotInObjectListJson(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/xmlnamenotinobjectlistjson", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/xmlnamenotinobjectlistjson", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
byteResults, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(strings.Contains(string(byteResults), "XML"), Equals, false)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestContentTypePersists(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/contenttype-persists", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/contenttype-persists/one", bytes.NewBufferString("hello world"))
|
||||
delete(request.Header, "Content-Type")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("HEAD", testAPIDonutCacheServer.URL+"/contenttype-persists/one", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/contenttype-persists/one", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/contenttype-persists/two", bytes.NewBufferString("hello world"))
|
||||
delete(request.Header, "Content-Type")
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("HEAD", testAPIDonutCacheServer.URL+"/contenttype-persists/two", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/contenttype-persists/two", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestPartialContent(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/partial-content", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/partial-content/bar", bytes.NewBufferString("Hello World"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// prepare request
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/partial-content/bar", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
request.Header.Add("Range", "bytes=6-7")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusPartialContent)
|
||||
partialObject, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Assert(string(partialObject), Equals, "Wo")
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestListObjectsHandlerErrors(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutCacheServer.URL+"/objecthandlererrors-.", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/objecthandlererrors", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist.", http.StatusNotFound)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/objecthandlererrors", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/objecthandlererrors?max-keys=-2", nil)
|
||||
c.Assert(err, IsNil)
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidArgument", "Argument maxKeys must be an integer between 0 and 2147483647.", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestPutBucketErrors(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/putbucket-.", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/putbucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/putbucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "BucketAlreadyExists", "The requested bucket name is not available.", http.StatusConflict)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/putbucket?acl", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "unknown")
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NotImplemented", "A header you provided implies functionality that is not implemented.", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestGetObjectErrors(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutCacheServer.URL+"/getobjecterrors", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist.", http.StatusNotFound)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/getobjecterrors", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/getobjecterrors/bar", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/getobjecterrors-./bar", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest)
|
||||
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestGetObjectRangeErrors(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/getobjectrangeerrors", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/getobjectrangeerrors/bar", bytes.NewBufferString("Hello World"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/getobjectrangeerrors/bar", nil)
|
||||
request.Header.Add("Range", "bytes=7-6")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidRange", "The requested range cannot be satisfied.", http.StatusRequestedRangeNotSatisfiable)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestObjectMultipartAbort(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/objectmultipartabort", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = http.NewRequest("POST", testAPIDonutCacheServer.URL+"/objectmultipartabort/object?uploads", bytes.NewBufferString(""))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/objectmultipartabort/object?uploadId="+uploadID+"&partNumber=1", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/objectmultipartabort/object?uploadId="+uploadID+"&partNumber=2", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("DELETE", testAPIDonutCacheServer.URL+"/objectmultipartabort/object?uploadId="+uploadID, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response3, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response3.StatusCode, Equals, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestBucketMultipartList(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/bucketmultipartlist", bytes.NewBufferString(""))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = http.NewRequest("POST", testAPIDonutCacheServer.URL+"/bucketmultipartlist/object?uploads", bytes.NewBufferString(""))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/bucketmultipartlist/object?uploadId="+uploadID+"&partNumber=1", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/bucketmultipartlist/object?uploadId="+uploadID+"&partNumber=2", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/bucketmultipartlist?uploads", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response3, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response3.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder = xml.NewDecoder(response3.Body)
|
||||
newResponse3 := &api.ListMultipartUploadsResponse{}
|
||||
err = decoder.Decode(newResponse3)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist")
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestObjectMultipartList(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/objectmultipartlist", bytes.NewBufferString(""))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = http.NewRequest("POST", testAPIDonutCacheServer.URL+"/objectmultipartlist/object?uploads", bytes.NewBufferString(""))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/objectmultipartlist/object?uploadId="+uploadID+"&partNumber=1", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/objectmultipartlist/object?uploadId="+uploadID+"&partNumber=2", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/objectmultipartlist/object?uploadId="+uploadID, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response3, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response3.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/objectmultipartlist/object?max-parts=-2&uploadId="+uploadID, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response4, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response4, "InvalidArgument", "Argument maxParts must be an integer between 1 and 10000.", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutCacheSuite) TestObjectMultipart(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/objectmultiparts", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = http.NewRequest("POST", testAPIDonutCacheServer.URL+"/objectmultiparts/object?uploads", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/objectmultiparts/object?uploadId="+uploadID+"&partNumber=1", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutCacheServer.URL+"/objectmultiparts/object?uploadId="+uploadID+"&partNumber=2", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// complete multipart upload
|
||||
completeUploads := &donut.CompleteMultipartUpload{
|
||||
Part: []donut.CompletePart{
|
||||
{
|
||||
PartNumber: 1,
|
||||
ETag: response1.Header.Get("ETag"),
|
||||
},
|
||||
{
|
||||
PartNumber: 2,
|
||||
ETag: response2.Header.Get("ETag"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var completeBuffer bytes.Buffer
|
||||
encoder := xml.NewEncoder(&completeBuffer)
|
||||
encoder.Encode(completeUploads)
|
||||
|
||||
request, err = http.NewRequest("POST", testAPIDonutCacheServer.URL+"/objectmultiparts/object?uploadId="+uploadID, &completeBuffer)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutCacheServer.URL+"/objectmultiparts/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
object, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(object), Equals, ("hello worldhello world"))
|
||||
}
|
||||
|
||||
func verifyError(c *C, response *http.Response, code, description string, statusCode int) {
|
||||
data, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
errorResponse := api.ErrorResponse{}
|
||||
err = xml.Unmarshal(data, &errorResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(errorResponse.Code, Equals, code)
|
||||
c.Assert(errorResponse.Message, Equals, description)
|
||||
c.Assert(response.StatusCode, Equals, statusCode)
|
||||
}
|
||||
@@ -1,908 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2014 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 server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
"github.com/minio/minio/pkg/server/api"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type MyAPIDonutSuite struct {
|
||||
root string
|
||||
}
|
||||
|
||||
var _ = Suite(&MyAPIDonutSuite{})
|
||||
|
||||
var testAPIDonutServer *httptest.Server
|
||||
|
||||
// create a dummy TestNodeDiskMap
|
||||
func createTestNodeDiskMap(p string) map[string][]string {
|
||||
nodes := make(map[string][]string)
|
||||
nodes["localhost"] = make([]string, 16)
|
||||
for i := 0; i < len(nodes["localhost"]); i++ {
|
||||
diskPath := filepath.Join(p, strconv.Itoa(i))
|
||||
if _, err := os.Stat(diskPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
os.MkdirAll(diskPath, 0700)
|
||||
}
|
||||
}
|
||||
nodes["localhost"][i] = diskPath
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) SetUpSuite(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "api-")
|
||||
c.Assert(err, IsNil)
|
||||
s.root = root
|
||||
|
||||
conf := new(donut.Config)
|
||||
conf.Version = "0.0.1"
|
||||
conf.DonutName = "test"
|
||||
conf.NodeDiskMap = createTestNodeDiskMap(root)
|
||||
conf.MaxSize = 100000
|
||||
donut.SetDonutConfigPath(filepath.Join(root, "donut.json"))
|
||||
perr := donut.SaveConfig(conf)
|
||||
c.Assert(perr, IsNil)
|
||||
|
||||
httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16})
|
||||
go startTM(minioAPI)
|
||||
testAPIDonutServer = httptest.NewServer(httpHandler)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TearDownSuite(c *C) {
|
||||
os.RemoveAll(s.root)
|
||||
testAPIDonutServer.Close()
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestDeleteBucket(c *C) {
|
||||
request, err := http.NewRequest("DELETE", testAPIDonutServer.URL+"/mybucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := &http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestDeleteObject(c *C) {
|
||||
request, err := http.NewRequest("DELETE", testAPIDonutServer.URL+"/mybucket/myobject", nil)
|
||||
c.Assert(err, IsNil)
|
||||
client := &http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestNonExistantBucket(c *C) {
|
||||
request, err := http.NewRequest("HEAD", testAPIDonutServer.URL+"/nonexistantbucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestEmptyObject(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/emptyobject", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/emptyobject/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/emptyobject/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, buffer.Bytes()))
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestBucket(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/bucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("HEAD", testAPIDonutServer.URL+"/bucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestObject(c *C) {
|
||||
buffer := bytes.NewBufferString("hello world")
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/testobject", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/testobject/object", buffer)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/testobject/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(responseBody, DeepEquals, []byte("hello world"))
|
||||
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestMultipleObjects(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/multipleobjects", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/multipleobjects/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound)
|
||||
|
||||
//// test object 1
|
||||
|
||||
// get object
|
||||
buffer1 := bytes.NewBufferString("hello one")
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/multipleobjects/object1", buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/multipleobjects/object1", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// verify response data
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello one")))
|
||||
|
||||
buffer2 := bytes.NewBufferString("hello two")
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/multipleobjects/object2", buffer2)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/multipleobjects/object2", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// verify response data
|
||||
responseBody, err = ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello two")))
|
||||
|
||||
buffer3 := bytes.NewBufferString("hello three")
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/multipleobjects/object3", buffer3)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/multipleobjects/object3", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// verify object
|
||||
responseBody, err = ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello three")))
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestNotImplemented(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutServer.URL+"/bucket/object?policy", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusNotImplemented)
|
||||
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestHeader(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutServer.URL+"/bucket/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestPutBucket(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/put-bucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestPutObject(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/put-object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/put-object/object", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestListBuckets(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutServer.URL+"/", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var results api.ListBucketsResponse
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
err = decoder.Decode(&results)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestNotBeAbleToCreateObjectInNonexistantBucket(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/innonexistantbucket/object", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist.", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestHeadOnObject(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/headonobject", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/headonobject/object1", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("HEAD", testAPIDonutServer.URL+"/headonobject/object1", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestHeadOnBucket(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/headonbucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("HEAD", testAPIDonutServer.URL+"/headonbucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
/*
|
||||
func (s *MyAPIDonutSuite) TestDateFormat(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/dateformat", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
// set an invalid date
|
||||
request.Header.Set("Date", "asfasdfadf")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "RequestTimeTooSkewed",
|
||||
"The difference between the request time and the server's time is too large.", http.StatusForbidden)
|
||||
|
||||
request.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
*/
|
||||
|
||||
func (s *MyAPIDonutSuite) TestXMLNameNotInBucketListJson(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutServer.URL+"/", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
byteResults, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(strings.Contains(string(byteResults), "XML"), Equals, false)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestXMLNameNotInObjectListJson(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/xmlnamenotinobjectlistjson", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/xmlnamenotinobjectlistjson", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
byteResults, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(strings.Contains(string(byteResults), "XML"), Equals, false)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestContentTypePersists(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/contenttype-persists", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/contenttype-persists/one", bytes.NewBufferString("hello world"))
|
||||
delete(request.Header, "Content-Type")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("HEAD", testAPIDonutServer.URL+"/contenttype-persists/one", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/contenttype-persists/one", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/contenttype-persists/two", bytes.NewBufferString("hello world"))
|
||||
delete(request.Header, "Content-Type")
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("HEAD", testAPIDonutServer.URL+"/contenttype-persists/two", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/contenttype-persists/two", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestPartialContent(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/partial-content", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/partial-content/bar", bytes.NewBufferString("Hello World"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// prepare request
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/partial-content/bar", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
request.Header.Add("Range", "bytes=6-7")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusPartialContent)
|
||||
partialObject, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Assert(string(partialObject), Equals, "Wo")
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestListObjectsHandlerErrors(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutServer.URL+"/objecthandlererrors-.", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/objecthandlererrors", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist.", http.StatusNotFound)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/objecthandlererrors", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/objecthandlererrors?max-keys=-2", nil)
|
||||
c.Assert(err, IsNil)
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidArgument", "Argument maxKeys must be an integer between 0 and 2147483647.", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestPutBucketErrors(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/putbucket-.", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/putbucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/putbucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "BucketAlreadyExists", "The requested bucket name is not available.", http.StatusConflict)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/putbucket?acl", nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "unknown")
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NotImplemented", "A header you provided implies functionality that is not implemented.", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestGetObjectErrors(c *C) {
|
||||
request, err := http.NewRequest("GET", testAPIDonutServer.URL+"/getobjecterrors", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist.", http.StatusNotFound)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/getobjecterrors", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/getobjecterrors/bar", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/getobjecterrors-./bar", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest)
|
||||
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestGetObjectRangeErrors(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/getobjectrangeerrors", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/getobjectrangeerrors/bar", bytes.NewBufferString("Hello World"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/getobjectrangeerrors/bar", nil)
|
||||
request.Header.Add("Range", "bytes=7-6")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidRange", "The requested range cannot be satisfied.", http.StatusRequestedRangeNotSatisfiable)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestObjectMultipartAbort(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/objectmultipartabort", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = http.NewRequest("POST", testAPIDonutServer.URL+"/objectmultipartabort/object?uploads", bytes.NewBufferString(""))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/objectmultipartabort/object?uploadId="+uploadID+"&partNumber=1", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/objectmultipartabort/object?uploadId="+uploadID+"&partNumber=2", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("DELETE", testAPIDonutServer.URL+"/objectmultipartabort/object?uploadId="+uploadID, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response3, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response3.StatusCode, Equals, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestBucketMultipartList(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/bucketmultipartlist", bytes.NewBufferString(""))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = http.NewRequest("POST", testAPIDonutServer.URL+"/bucketmultipartlist/object?uploads", bytes.NewBufferString(""))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/bucketmultipartlist/object?uploadId="+uploadID+"&partNumber=1", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/bucketmultipartlist/object?uploadId="+uploadID+"&partNumber=2", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/bucketmultipartlist?uploads", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response3, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response3.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder = xml.NewDecoder(response3.Body)
|
||||
newResponse3 := &api.ListMultipartUploadsResponse{}
|
||||
err = decoder.Decode(newResponse3)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist")
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestObjectMultipartList(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/objectmultipartlist", bytes.NewBufferString(""))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = http.NewRequest("POST", testAPIDonutServer.URL+"/objectmultipartlist/object?uploads", bytes.NewBufferString(""))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/objectmultipartlist/object?uploadId="+uploadID+"&partNumber=1", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/objectmultipartlist/object?uploadId="+uploadID+"&partNumber=2", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/objectmultipartlist/object?uploadId="+uploadID, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response3, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response3.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/objectmultipartlist/object?max-parts=-2&uploadId="+uploadID, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response4, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response4, "InvalidArgument", "Argument maxParts must be an integer between 1 and 10000.", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func (s *MyAPIDonutSuite) TestObjectMultipart(c *C) {
|
||||
request, err := http.NewRequest("PUT", testAPIDonutServer.URL+"/objectmultiparts", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = http.NewRequest("POST", testAPIDonutServer.URL+"/objectmultiparts/object?uploads", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/objectmultiparts/object?uploadId="+uploadID+"&partNumber=1", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("PUT", testAPIDonutServer.URL+"/objectmultiparts/object?uploadId="+uploadID+"&partNumber=2", bytes.NewBufferString("hello world"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// complete multipart upload
|
||||
completeUploads := &donut.CompleteMultipartUpload{
|
||||
Part: []donut.CompletePart{
|
||||
{
|
||||
PartNumber: 1,
|
||||
ETag: response1.Header.Get("ETag"),
|
||||
},
|
||||
{
|
||||
PartNumber: 2,
|
||||
ETag: response2.Header.Get("ETag"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var completeBuffer bytes.Buffer
|
||||
encoder := xml.NewEncoder(&completeBuffer)
|
||||
encoder.Encode(completeUploads)
|
||||
|
||||
request, err = http.NewRequest("POST", testAPIDonutServer.URL+"/objectmultiparts/object?uploadId="+uploadID, &completeBuffer)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
/*
|
||||
request, err = http.NewRequest("GET", testAPIDonutServer.URL+"/objectmultiparts/object", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
object, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(object), Equals, ("hello worldhello world"))
|
||||
*/
|
||||
}
|
||||
@@ -1,908 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2014 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 server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/donut"
|
||||
"github.com/minio/minio/pkg/server/api"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type MyAPISignatureV4Suite struct {
|
||||
root string
|
||||
req *http.Request
|
||||
body io.ReadSeeker
|
||||
accessKeyID string
|
||||
secretAccessKey string
|
||||
}
|
||||
|
||||
var _ = Suite(&MyAPISignatureV4Suite{})
|
||||
|
||||
var testSignatureV4Server *httptest.Server
|
||||
|
||||
func (s *MyAPISignatureV4Suite) SetUpSuite(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "api-")
|
||||
c.Assert(err, IsNil)
|
||||
s.root = root
|
||||
|
||||
conf := &donut.Config{}
|
||||
conf.Version = "0.0.1"
|
||||
conf.DonutName = "test"
|
||||
conf.NodeDiskMap = createTestNodeDiskMap(root)
|
||||
conf.MaxSize = 100000
|
||||
donut.SetDonutConfigPath(filepath.Join(root, "donut.json"))
|
||||
perr := donut.SaveConfig(conf)
|
||||
c.Assert(perr, IsNil)
|
||||
|
||||
accessKeyID, perr := auth.GenerateAccessKeyID()
|
||||
c.Assert(perr, IsNil)
|
||||
secretAccessKey, perr := auth.GenerateSecretAccessKey()
|
||||
c.Assert(perr, IsNil)
|
||||
|
||||
authConf := &auth.Config{}
|
||||
authConf.Users = make(map[string]*auth.User)
|
||||
authConf.Users[string(accessKeyID)] = &auth.User{
|
||||
Name: "testuser",
|
||||
AccessKeyID: string(accessKeyID),
|
||||
SecretAccessKey: string(secretAccessKey),
|
||||
}
|
||||
s.accessKeyID = string(accessKeyID)
|
||||
s.secretAccessKey = string(secretAccessKey)
|
||||
|
||||
auth.SetAuthConfigPath(root)
|
||||
perr = auth.SaveConfig(authConf)
|
||||
c.Assert(perr, IsNil)
|
||||
|
||||
httpHandler, minioAPI := getAPIHandler(api.Config{RateLimit: 16})
|
||||
go startTM(minioAPI)
|
||||
testSignatureV4Server = httptest.NewServer(httpHandler)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TearDownSuite(c *C) {
|
||||
os.RemoveAll(s.root)
|
||||
testSignatureV4Server.Close()
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestDeleteBucket(c *C) {
|
||||
request, err := http.NewRequest("DELETE", testSignatureV4Server.URL+"/mybucket", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := &http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestDeleteObject(c *C) {
|
||||
request, err := http.NewRequest("DELETE", testSignatureV4Server.URL+"/mybucket/myobject", nil)
|
||||
c.Assert(err, IsNil)
|
||||
client := &http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestNonExistantBucket(c *C) {
|
||||
request, err := s.newRequest("HEAD", testSignatureV4Server.URL+"/nonexistantbucket", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestEmptyObject(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/emptyobject", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/emptyobject/object", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/emptyobject/object", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, buffer.Bytes()))
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestBucket(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/bucket", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("HEAD", testSignatureV4Server.URL+"/bucket", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestObject(c *C) {
|
||||
buffer := bytes.NewReader([]byte("hello world"))
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/testobject", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/testobject/object", int64(buffer.Len()), buffer)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/testobject/object", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(responseBody, DeepEquals, []byte("hello world"))
|
||||
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestMultipleObjects(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/multipleobjects", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/multipleobjects/object", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound)
|
||||
|
||||
//// test object 1
|
||||
|
||||
// get object
|
||||
buffer1 := bytes.NewReader([]byte("hello one"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/multipleobjects/object1", int64(buffer1.Len()), buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/multipleobjects/object1", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// verify response data
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello one")))
|
||||
|
||||
buffer2 := bytes.NewReader([]byte("hello two"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/multipleobjects/object2", int64(buffer2.Len()), buffer2)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/multipleobjects/object2", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// verify response data
|
||||
responseBody, err = ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello two")))
|
||||
|
||||
buffer3 := bytes.NewReader([]byte("hello three"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/multipleobjects/object3", int64(buffer3.Len()), buffer3)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/multipleobjects/object3", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// verify object
|
||||
responseBody, err = ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello three")))
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestNotImplemented(c *C) {
|
||||
request, err := s.newRequest("GET", testSignatureV4Server.URL+"/bucket/object?policy", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusNotImplemented)
|
||||
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestHeader(c *C) {
|
||||
request, err := s.newRequest("GET", testSignatureV4Server.URL+"/bucket/object", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestPutBucket(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/put-bucket", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestPutObject(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/put-object", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
buffer1 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/put-object/object", int64(buffer1.Len()), buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestListBuckets(c *C) {
|
||||
request, err := s.newRequest("GET", testSignatureV4Server.URL+"/", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
var results api.ListBucketsResponse
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
err = decoder.Decode(&results)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestNotBeAbleToCreateObjectInNonexistantBucket(c *C) {
|
||||
buffer1 := bytes.NewReader([]byte("hello world"))
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/innonexistantbucket/object", int64(buffer1.Len()), buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist.", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestHeadOnObject(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/headonobject", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
buffer1 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/headonobject/object1", int64(buffer1.Len()), buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("HEAD", testSignatureV4Server.URL+"/headonobject/object1", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestHeadOnBucket(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/headonbucket", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("HEAD", testSignatureV4Server.URL+"/headonbucket", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestXMLNameNotInBucketListJson(c *C) {
|
||||
request, err := s.newRequest("GET", testSignatureV4Server.URL+"/", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
byteResults, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(strings.Contains(string(byteResults), "XML"), Equals, false)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestXMLNameNotInObjectListJson(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/xmlnamenotinobjectlistjson", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/xmlnamenotinobjectlistjson", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
byteResults, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(strings.Contains(string(byteResults), "XML"), Equals, false)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestContentTypePersists(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/contenttype-persists", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
buffer1 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/contenttype-persists/one", int64(buffer1.Len()), buffer1)
|
||||
delete(request.Header, "Content-Type")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("HEAD", testSignatureV4Server.URL+"/contenttype-persists/one", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/contenttype-persists/one", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
|
||||
buffer2 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/contenttype-persists/two", int64(buffer2.Len()), buffer2)
|
||||
delete(request.Header, "Content-Type")
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("HEAD", testSignatureV4Server.URL+"/contenttype-persists/two", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/contenttype-persists/two", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.Header.Get("Content-Type"), Equals, "application/octet-stream")
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestPartialContent(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/partial-content", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
buffer1 := bytes.NewReader([]byte("Hello World"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/partial-content/bar", int64(buffer1.Len()), buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// prepare request
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/partial-content/bar", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
request.Header.Add("Range", "bytes=6-7")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusPartialContent)
|
||||
partialObject, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Assert(string(partialObject), Equals, "Wo")
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestListObjectsHandlerErrors(c *C) {
|
||||
request, err := s.newRequest("GET", testSignatureV4Server.URL+"/objecthandlererrors-.", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/objecthandlererrors", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist.", http.StatusNotFound)
|
||||
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/objecthandlererrors", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testSignatureV4Server.URL+"/objecthandlererrors?max-keys=-2", nil)
|
||||
c.Assert(err, IsNil)
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidArgument", "Argument maxKeys must be an integer between 0 and 2147483647.", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestPutBucketErrors(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/putbucket-.", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest)
|
||||
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/putbucket", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/putbucket", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "private")
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "BucketAlreadyExists", "The requested bucket name is not available.", http.StatusConflict)
|
||||
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/putbucket?acl", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
request.Header.Add("x-amz-acl", "unknown")
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NotImplemented", "A header you provided implies functionality that is not implemented.", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestGetObjectErrors(c *C) {
|
||||
request, err := s.newRequest("GET", testSignatureV4Server.URL+"/getobjecterrors", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist.", http.StatusNotFound)
|
||||
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/getobjecterrors", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/getobjecterrors/bar", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/getobjecterrors-./bar", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest)
|
||||
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestGetObjectRangeErrors(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/getobjectrangeerrors", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
buffer1 := bytes.NewReader([]byte("Hello World"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/getobjectrangeerrors/bar", int64(buffer1.Len()), buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/getobjectrangeerrors/bar", 0, nil)
|
||||
request.Header.Add("Range", "bytes=7-6")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response, "InvalidRange", "The requested range cannot be satisfied.", http.StatusRequestedRangeNotSatisfiable)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestObjectMultipartAbort(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/objectmultipartabort", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = s.newRequest("POST", testSignatureV4Server.URL+"/objectmultipartabort/object?uploads", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
buffer1 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/objectmultipartabort/object?uploadId="+uploadID+"&partNumber=1", int64(buffer1.Len()), buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
buffer2 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/objectmultipartabort/object?uploadId="+uploadID+"&partNumber=2", int64(buffer2.Len()), buffer2)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("DELETE", testSignatureV4Server.URL+"/objectmultipartabort/object?uploadId="+uploadID, 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response3, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response3.StatusCode, Equals, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestBucketMultipartList(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/bucketmultipartlist", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = s.newRequest("POST", testSignatureV4Server.URL+"/bucketmultipartlist/object?uploads", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
buffer1 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/bucketmultipartlist/object?uploadId="+uploadID+"&partNumber=1", int64(buffer1.Len()), buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
buffer2 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/bucketmultipartlist/object?uploadId="+uploadID+"&partNumber=2", int64(buffer2.Len()), buffer2)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/bucketmultipartlist?uploads", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response3, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response3.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder = xml.NewDecoder(response3.Body)
|
||||
newResponse3 := &api.ListMultipartUploadsResponse{}
|
||||
err = decoder.Decode(newResponse3)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(newResponse3.Bucket, Equals, "bucketmultipartlist")
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestObjectMultipartList(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/objectmultipartlist", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = s.newRequest("POST", testSignatureV4Server.URL+"/objectmultipartlist/object?uploads", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
buffer1 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/objectmultipartlist/object?uploadId="+uploadID+"&partNumber=1", int64(buffer1.Len()), buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
buffer2 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/objectmultipartlist/object?uploadId="+uploadID+"&partNumber=2", int64(buffer2.Len()), buffer2)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/objectmultipartlist/object?uploadId="+uploadID, 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response3, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response3.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
request, err = http.NewRequest("GET", testSignatureV4Server.URL+"/objectmultipartlist/object?max-parts=-2&uploadId="+uploadID, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response4, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
verifyError(c, response4, "InvalidArgument", "Argument maxParts must be an integer between 1 and 10000.", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) TestObjectMultipart(c *C) {
|
||||
request, err := s.newRequest("PUT", testSignatureV4Server.URL+"/objectmultiparts", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client := http.Client{}
|
||||
response, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, 200)
|
||||
|
||||
request, err = s.newRequest("POST", testSignatureV4Server.URL+"/objectmultiparts/object?uploads", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
newResponse := &api.InitiateMultipartUploadResponse{}
|
||||
|
||||
err = decoder.Decode(newResponse)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(len(newResponse.UploadID) > 0, Equals, true)
|
||||
uploadID := newResponse.UploadID
|
||||
|
||||
buffer1 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/objectmultiparts/object?uploadId="+uploadID+"&partNumber=1", int64(buffer1.Len()), buffer1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response1, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response1.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
buffer2 := bytes.NewReader([]byte("hello world"))
|
||||
request, err = s.newRequest("PUT", testSignatureV4Server.URL+"/objectmultiparts/object?uploadId="+uploadID+"&partNumber=2", int64(buffer2.Len()), buffer2)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
client = http.Client{}
|
||||
response2, err := client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response2.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
// complete multipart upload
|
||||
completeUploads := &donut.CompleteMultipartUpload{
|
||||
Part: []donut.CompletePart{
|
||||
{
|
||||
PartNumber: 1,
|
||||
ETag: response1.Header.Get("ETag"),
|
||||
},
|
||||
{
|
||||
PartNumber: 2,
|
||||
ETag: response2.Header.Get("ETag"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
completeBytes, err := xml.Marshal(completeUploads)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
request, err = s.newRequest("POST", testSignatureV4Server.URL+"/objectmultiparts/object?uploadId="+uploadID, int64(len(completeBytes)), bytes.NewReader(completeBytes))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
/*
|
||||
request, err = s.newRequest("GET", testSignatureV4Server.URL+"/objectmultiparts/object", 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||
object, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(object), Equals, ("hello worldhello world"))
|
||||
*/
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2014 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 server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
router "github.com/gorilla/mux"
|
||||
"github.com/minio/minio/pkg/server/api"
|
||||
)
|
||||
|
||||
// registerAPI - register all the object API handlers to their respective paths
|
||||
func registerAPI(mux *router.Router, a api.Minio) {
|
||||
mux.HandleFunc("/", a.ListBucketsHandler).Methods("GET")
|
||||
mux.HandleFunc("/{bucket}", a.ListObjectsHandler).Methods("GET")
|
||||
mux.HandleFunc("/{bucket}", a.PutBucketHandler).Methods("PUT")
|
||||
mux.HandleFunc("/{bucket}", a.HeadBucketHandler).Methods("HEAD")
|
||||
mux.HandleFunc("/{bucket}/{object:.*}", a.HeadObjectHandler).Methods("HEAD")
|
||||
mux.HandleFunc("/{bucket}/{object:.*}", a.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}").Methods("PUT")
|
||||
mux.HandleFunc("/{bucket}/{object:.*}", a.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}").Methods("GET")
|
||||
mux.HandleFunc("/{bucket}/{object:.*}", a.CompleteMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}").Methods("POST")
|
||||
mux.HandleFunc("/{bucket}/{object:.*}", a.NewMultipartUploadHandler).Methods("POST")
|
||||
mux.HandleFunc("/{bucket}/{object:.*}", a.AbortMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}").Methods("DELETE")
|
||||
mux.HandleFunc("/{bucket}/{object:.*}", a.GetObjectHandler).Methods("GET")
|
||||
mux.HandleFunc("/{bucket}/{object:.*}", a.PutObjectHandler).Methods("PUT")
|
||||
|
||||
// not implemented yet
|
||||
mux.HandleFunc("/{bucket}", a.DeleteBucketHandler).Methods("DELETE")
|
||||
|
||||
// unsupported API
|
||||
mux.HandleFunc("/{bucket}/{object:.*}", a.DeleteObjectHandler).Methods("DELETE")
|
||||
}
|
||||
|
||||
func registerCustomMiddleware(mux *router.Router, mwHandlers ...api.MiddlewareHandler) http.Handler {
|
||||
var f http.Handler
|
||||
f = mux
|
||||
for _, mw := range mwHandlers {
|
||||
f = mw(f)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// getAPIHandler api handler
|
||||
func getAPIHandler(conf api.Config) (http.Handler, api.Minio) {
|
||||
var mwHandlers = []api.MiddlewareHandler{
|
||||
api.ValidContentTypeHandler,
|
||||
api.TimeValidityHandler,
|
||||
api.IgnoreResourcesHandler,
|
||||
api.ValidateAuthHeaderHandler,
|
||||
// api.LoggingHandler, // Disabled logging until we bring in external logging support
|
||||
api.CorsHandler,
|
||||
}
|
||||
|
||||
mux := router.NewRouter()
|
||||
minioAPI := api.New()
|
||||
registerAPI(mux, minioAPI)
|
||||
apiHandler := registerCustomMiddleware(mux, mwHandlers...)
|
||||
return apiHandler, minioAPI
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2014 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 server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/minhttp"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
"github.com/minio/minio/pkg/server/api"
|
||||
)
|
||||
|
||||
// getAPI server instance
|
||||
func getAPIServer(conf api.Config, apiHandler http.Handler) (*http.Server, *probe.Error) {
|
||||
// Minio server config
|
||||
httpServer := &http.Server{
|
||||
Addr: conf.Address,
|
||||
Handler: apiHandler,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
|
||||
if conf.TLS {
|
||||
var err error
|
||||
httpServer.TLSConfig = &tls.Config{}
|
||||
httpServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
||||
httpServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(conf.CertFile, conf.KeyFile)
|
||||
if err != nil {
|
||||
return nil, probe.NewError(err)
|
||||
}
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(conf.Address)
|
||||
if err != nil {
|
||||
return nil, probe.NewError(err)
|
||||
}
|
||||
|
||||
var hosts []string
|
||||
switch {
|
||||
case host != "":
|
||||
hosts = append(hosts, host)
|
||||
default:
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, probe.NewError(err)
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if addr.Network() == "ip+net" {
|
||||
host := strings.Split(addr.String(), "/")[0]
|
||||
if ip := net.ParseIP(host); ip.To4() != nil {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if conf.TLS {
|
||||
fmt.Printf("Starting minio server on: https://%s:%s, PID: %d\n", host, port, os.Getpid())
|
||||
} else {
|
||||
fmt.Printf("Starting minio server on: http://%s:%s, PID: %d\n", host, port, os.Getpid())
|
||||
}
|
||||
|
||||
}
|
||||
return httpServer, nil
|
||||
}
|
||||
|
||||
// Start ticket master
|
||||
func startTM(a api.Minio) {
|
||||
for {
|
||||
for op := range a.OP {
|
||||
op.ProceedCh <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts a s3 compatible cloud storage server
|
||||
func Start(conf api.Config) *probe.Error {
|
||||
apiHandler, minioAPI := getAPIHandler(conf)
|
||||
apiServer, err := getAPIServer(conf, apiHandler)
|
||||
if err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
// start ticket master
|
||||
go startTM(minioAPI)
|
||||
if err := minhttp.ListenAndServeLimited(conf.RateLimit, apiServer); err != nil {
|
||||
return err.Trace()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2014 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 server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Hook up gocheck into the "go test" runner.
|
||||
func Test(t *testing.T) { TestingT(t) }
|
||||
|
||||
const (
|
||||
authHeader = "AWS4-HMAC-SHA256"
|
||||
iso8601Format = "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,
|
||||
}
|
||||
|
||||
// urlEncodedName 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 urlEncodeName(name string) (string, error) {
|
||||
// if object matches reserved string, no need to encode them
|
||||
reservedNames := regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
|
||||
if reservedNames.MatchString(name) {
|
||||
return name, nil
|
||||
}
|
||||
var encodedName string
|
||||
for _, s := range name {
|
||||
if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
|
||||
encodedName = encodedName + string(s)
|
||||
continue
|
||||
}
|
||||
switch s {
|
||||
case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
|
||||
encodedName = encodedName + string(s)
|
||||
continue
|
||||
default:
|
||||
len := utf8.RuneLen(s)
|
||||
if len < 0 {
|
||||
return "", errors.New("invalid utf-8")
|
||||
}
|
||||
u := make([]byte, len)
|
||||
utf8.EncodeRune(u, s)
|
||||
for _, r := range u {
|
||||
hex := hex.EncodeToString([]byte{r})
|
||||
encodedName = encodedName + "%" + strings.ToUpper(hex)
|
||||
}
|
||||
}
|
||||
}
|
||||
return encodedName, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (s *MyAPISignatureV4Suite) newRequest(method, urlStr string, contentLength int64, body io.ReadSeeker) (*http.Request, error) {
|
||||
t := time.Now().UTC()
|
||||
req, err := http.NewRequest(method, urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("x-amz-date", t.Format(iso8601Format))
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
// add Content-Length
|
||||
req.ContentLength = contentLength
|
||||
|
||||
// add body
|
||||
switch {
|
||||
case body == nil:
|
||||
req.Body = nil
|
||||
default:
|
||||
req.Body = ioutil.NopCloser(body)
|
||||
}
|
||||
|
||||
// save for subsequent use
|
||||
hash := func() string {
|
||||
switch {
|
||||
case body == nil:
|
||||
return hex.EncodeToString(sum256([]byte{}))
|
||||
default:
|
||||
sum256Bytes, _ := sum256Reader(body)
|
||||
return hex.EncodeToString(sum256Bytes)
|
||||
}
|
||||
}
|
||||
hashedPayload := hash()
|
||||
req.Header.Set("x-amz-content-sha256", hashedPayload)
|
||||
|
||||
var headers []string
|
||||
vals := make(map[string][]string)
|
||||
for k, vv := range 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 canonicalHeaders bytes.Buffer
|
||||
for _, k := range headers {
|
||||
canonicalHeaders.WriteString(k)
|
||||
canonicalHeaders.WriteByte(':')
|
||||
switch {
|
||||
case k == "host":
|
||||
canonicalHeaders.WriteString(req.URL.Host)
|
||||
fallthrough
|
||||
default:
|
||||
for idx, v := range vals[k] {
|
||||
if idx > 0 {
|
||||
canonicalHeaders.WriteByte(',')
|
||||
}
|
||||
canonicalHeaders.WriteString(v)
|
||||
}
|
||||
canonicalHeaders.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
signedHeaders := strings.Join(headers, ";")
|
||||
|
||||
req.URL.RawQuery = strings.Replace(req.URL.Query().Encode(), "+", "%20", -1)
|
||||
encodedPath, _ := urlEncodeName(req.URL.Path)
|
||||
// convert any space strings back to "+"
|
||||
encodedPath = strings.Replace(encodedPath, "+", "%20", -1)
|
||||
|
||||
//
|
||||
// canonicalRequest =
|
||||
// <HTTPMethod>\n
|
||||
// <CanonicalURI>\n
|
||||
// <CanonicalQueryString>\n
|
||||
// <CanonicalHeaders>\n
|
||||
// <SignedHeaders>\n
|
||||
// <HashedPayload>
|
||||
//
|
||||
canonicalRequest := strings.Join([]string{
|
||||
req.Method,
|
||||
encodedPath,
|
||||
req.URL.RawQuery,
|
||||
canonicalHeaders.String(),
|
||||
signedHeaders,
|
||||
hashedPayload,
|
||||
}, "\n")
|
||||
|
||||
scope := strings.Join([]string{
|
||||
t.Format(yyyymmdd),
|
||||
"milkyway",
|
||||
"s3",
|
||||
"aws4_request",
|
||||
}, "/")
|
||||
|
||||
stringToSign := authHeader + "\n" + t.Format(iso8601Format) + "\n"
|
||||
stringToSign = stringToSign + scope + "\n"
|
||||
stringToSign = stringToSign + hex.EncodeToString(sum256([]byte(canonicalRequest)))
|
||||
|
||||
date := sumHMAC([]byte("AWS4"+s.secretAccessKey), []byte(t.Format(yyyymmdd)))
|
||||
region := sumHMAC(date, []byte("milkyway"))
|
||||
service := sumHMAC(region, []byte("s3"))
|
||||
signingKey := sumHMAC(service, []byte("aws4_request"))
|
||||
|
||||
signature := hex.EncodeToString(sumHMAC(signingKey, []byte(stringToSign)))
|
||||
|
||||
// final Authorization header
|
||||
parts := []string{
|
||||
authHeader + " Credential=" + s.accessKeyID + "/" + scope,
|
||||
"SignedHeaders=" + signedHeaders,
|
||||
"Signature=" + signature,
|
||||
}
|
||||
auth := strings.Join(parts, ", ")
|
||||
req.Header.Set("Authorization", auth)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
Reference in New Issue
Block a user