forked from TrueCloudLab/distribution
Move to vendor
Signed-off-by: Olivier Gambier <olivier@docker.com>
This commit is contained in:
parent
c8d8e7e357
commit
77e69b9cf3
1268 changed files with 34 additions and 24 deletions
941
vendor/github.com/ncw/swift/swifttest/server.go
generated
vendored
Normal file
941
vendor/github.com/ncw/swift/swifttest/server.go
generated
vendored
Normal file
|
@ -0,0 +1,941 @@
|
|||
// This implements a very basic Swift server
|
||||
// Everything is stored in memory
|
||||
//
|
||||
// This comes from the https://github.com/mitchellh/goamz
|
||||
// and was adapted for Swift
|
||||
//
|
||||
package swifttest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ncw/swift"
|
||||
)
|
||||
|
||||
const (
|
||||
DEBUG = false
|
||||
TEST_ACCOUNT = "swifttest"
|
||||
)
|
||||
|
||||
type SwiftServer struct {
|
||||
t *testing.T
|
||||
reqId int
|
||||
mu sync.Mutex
|
||||
Listener net.Listener
|
||||
AuthURL string
|
||||
URL string
|
||||
Accounts map[string]*account
|
||||
Sessions map[string]*session
|
||||
}
|
||||
|
||||
// The Folder type represents a container stored in an account
|
||||
type Folder struct {
|
||||
Count int `json:"count"`
|
||||
Bytes int `json:"bytes"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// The Key type represents an item stored in an container.
|
||||
type Key struct {
|
||||
Key string `json:"name"`
|
||||
LastModified string `json:"last_modified"`
|
||||
Size int64 `json:"bytes"`
|
||||
// ETag gives the hex-encoded MD5 sum of the contents,
|
||||
// surrounded with double-quotes.
|
||||
ETag string `json:"hash"`
|
||||
ContentType string `json:"content_type"`
|
||||
// Owner Owner
|
||||
}
|
||||
|
||||
type Subdir struct {
|
||||
Subdir string `json:"subdir"`
|
||||
}
|
||||
|
||||
type swiftError struct {
|
||||
statusCode int
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
type action struct {
|
||||
srv *SwiftServer
|
||||
w http.ResponseWriter
|
||||
req *http.Request
|
||||
reqId string
|
||||
user *account
|
||||
}
|
||||
|
||||
type session struct {
|
||||
username string
|
||||
}
|
||||
|
||||
type metadata struct {
|
||||
meta http.Header // metadata to return with requests.
|
||||
}
|
||||
|
||||
type account struct {
|
||||
swift.Account
|
||||
metadata
|
||||
password string
|
||||
Containers map[string]*container
|
||||
}
|
||||
|
||||
type object struct {
|
||||
metadata
|
||||
name string
|
||||
mtime time.Time
|
||||
checksum []byte // also held as ETag in meta.
|
||||
data []byte
|
||||
content_type string
|
||||
}
|
||||
|
||||
type container struct {
|
||||
metadata
|
||||
name string
|
||||
ctime time.Time
|
||||
objects map[string]*object
|
||||
bytes int
|
||||
}
|
||||
|
||||
// A resource encapsulates the subject of an HTTP request.
|
||||
// The resource referred to may or may not exist
|
||||
// when the request is made.
|
||||
type resource interface {
|
||||
put(a *action) interface{}
|
||||
get(a *action) interface{}
|
||||
post(a *action) interface{}
|
||||
delete(a *action) interface{}
|
||||
copy(a *action) interface{}
|
||||
}
|
||||
|
||||
type objectResource struct {
|
||||
name string
|
||||
version string
|
||||
container *container // always non-nil.
|
||||
object *object // may be nil.
|
||||
}
|
||||
|
||||
type containerResource struct {
|
||||
name string
|
||||
container *container // non-nil if the container already exists.
|
||||
}
|
||||
|
||||
var responseParams = map[string]bool{
|
||||
"content-type": true,
|
||||
"content-language": true,
|
||||
"expires": true,
|
||||
"cache-control": true,
|
||||
"content-disposition": true,
|
||||
"content-encoding": true,
|
||||
}
|
||||
|
||||
func fatalf(code int, codeStr string, errf string, a ...interface{}) {
|
||||
panic(&swiftError{
|
||||
statusCode: code,
|
||||
Code: codeStr,
|
||||
Message: fmt.Sprintf(errf, a...),
|
||||
})
|
||||
}
|
||||
|
||||
func (m metadata) setMetadata(a *action, resource string) {
|
||||
for key, values := range a.req.Header {
|
||||
key = http.CanonicalHeaderKey(key)
|
||||
if metaHeaders[key] || strings.HasPrefix(key, "X-"+strings.Title(resource)+"-Meta-") {
|
||||
if values[0] != "" || resource == "object" {
|
||||
m.meta[key] = values
|
||||
} else {
|
||||
m.meta.Del(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m metadata) getMetadata(a *action) {
|
||||
h := a.w.Header()
|
||||
for name, d := range m.meta {
|
||||
h[name] = d
|
||||
}
|
||||
}
|
||||
|
||||
func (c container) list(delimiter string, marker string, prefix string, parent string) (resp []interface{}) {
|
||||
var tmp orderedObjects
|
||||
|
||||
// first get all matching objects and arrange them in alphabetical order.
|
||||
for _, obj := range c.objects {
|
||||
if strings.HasPrefix(obj.name, prefix) {
|
||||
tmp = append(tmp, obj)
|
||||
}
|
||||
}
|
||||
sort.Sort(tmp)
|
||||
|
||||
var prefixes []string
|
||||
for _, obj := range tmp {
|
||||
if !strings.HasPrefix(obj.name, prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
isPrefix := false
|
||||
name := obj.name
|
||||
if parent != "" {
|
||||
if path.Dir(obj.name) != path.Clean(parent) {
|
||||
continue
|
||||
}
|
||||
} else if delimiter != "" {
|
||||
if i := strings.Index(obj.name[len(prefix):], delimiter); i >= 0 {
|
||||
name = obj.name[:len(prefix)+i+len(delimiter)]
|
||||
if prefixes != nil && prefixes[len(prefixes)-1] == name {
|
||||
continue
|
||||
}
|
||||
isPrefix = true
|
||||
}
|
||||
}
|
||||
|
||||
if name <= marker {
|
||||
continue
|
||||
}
|
||||
|
||||
if isPrefix {
|
||||
prefixes = append(prefixes, name)
|
||||
|
||||
resp = append(resp, Subdir{
|
||||
Subdir: name,
|
||||
})
|
||||
} else {
|
||||
resp = append(resp, obj)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GET on a container lists the objects in the container.
|
||||
func (r containerResource) get(a *action) interface{} {
|
||||
if r.container == nil {
|
||||
fatalf(404, "NoSuchContainer", "The specified container does not exist")
|
||||
}
|
||||
|
||||
delimiter := a.req.Form.Get("delimiter")
|
||||
marker := a.req.Form.Get("marker")
|
||||
prefix := a.req.Form.Get("prefix")
|
||||
format := a.req.URL.Query().Get("format")
|
||||
parent := a.req.Form.Get("path")
|
||||
|
||||
a.w.Header().Set("X-Container-Bytes-Used", strconv.Itoa(r.container.bytes))
|
||||
a.w.Header().Set("X-Container-Object-Count", strconv.Itoa(len(r.container.objects)))
|
||||
r.container.getMetadata(a)
|
||||
|
||||
if a.req.Method == "HEAD" {
|
||||
return nil
|
||||
}
|
||||
|
||||
objects := r.container.list(delimiter, marker, prefix, parent)
|
||||
|
||||
if format == "json" {
|
||||
a.w.Header().Set("Content-Type", "application/json")
|
||||
var resp []interface{}
|
||||
for _, item := range objects {
|
||||
if obj, ok := item.(*object); ok {
|
||||
resp = append(resp, obj.Key())
|
||||
} else {
|
||||
resp = append(resp, item)
|
||||
}
|
||||
}
|
||||
return resp
|
||||
} else {
|
||||
for _, item := range objects {
|
||||
if obj, ok := item.(*object); ok {
|
||||
a.w.Write([]byte(obj.name + "\n"))
|
||||
} else if subdir, ok := item.(Subdir); ok {
|
||||
a.w.Write([]byte(subdir.Subdir + "\n"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// orderedContainers holds a slice of containers that can be sorted
|
||||
// by name.
|
||||
type orderedContainers []*container
|
||||
|
||||
func (s orderedContainers) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s orderedContainers) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
func (s orderedContainers) Less(i, j int) bool {
|
||||
return s[i].name < s[j].name
|
||||
}
|
||||
|
||||
func (r containerResource) delete(a *action) interface{} {
|
||||
b := r.container
|
||||
if b == nil {
|
||||
fatalf(404, "NoSuchContainer", "The specified container does not exist")
|
||||
}
|
||||
if len(b.objects) > 0 {
|
||||
fatalf(409, "Conflict", "The container you tried to delete is not empty")
|
||||
}
|
||||
delete(a.user.Containers, b.name)
|
||||
a.user.Account.Containers--
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r containerResource) put(a *action) interface{} {
|
||||
if a.req.URL.Query().Get("extract-archive") != "" {
|
||||
fatalf(403, "Operation forbidden", "Bulk upload is not supported")
|
||||
}
|
||||
|
||||
if r.container == nil {
|
||||
if !validContainerName(r.name) {
|
||||
fatalf(400, "InvalidContainerName", "The specified container is not valid")
|
||||
}
|
||||
r.container = &container{
|
||||
name: r.name,
|
||||
objects: make(map[string]*object),
|
||||
metadata: metadata{
|
||||
meta: make(http.Header),
|
||||
},
|
||||
}
|
||||
r.container.setMetadata(a, "container")
|
||||
a.user.Containers[r.name] = r.container
|
||||
a.user.Account.Containers++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r containerResource) post(a *action) interface{} {
|
||||
if r.container == nil {
|
||||
fatalf(400, "Method", "The resource could not be found.")
|
||||
} else {
|
||||
r.container.setMetadata(a, "container")
|
||||
a.w.WriteHeader(201)
|
||||
jsonMarshal(a.w, Folder{
|
||||
Count: len(r.container.objects),
|
||||
Bytes: r.container.bytes,
|
||||
Name: r.container.name,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (containerResource) copy(a *action) interface{} { return notAllowed() }
|
||||
|
||||
// validContainerName returns whether name is a valid bucket name.
|
||||
// Here are the rules, from:
|
||||
// http://docs.openstack.org/api/openstack-object-storage/1.0/content/ch_object-storage-dev-api-storage.html
|
||||
//
|
||||
// Container names cannot exceed 256 bytes and cannot contain the / character.
|
||||
//
|
||||
func validContainerName(name string) bool {
|
||||
if len(name) == 0 || len(name) > 256 {
|
||||
return false
|
||||
}
|
||||
for _, r := range name {
|
||||
switch {
|
||||
case r == '/':
|
||||
return false
|
||||
default:
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// orderedObjects holds a slice of objects that can be sorted
|
||||
// by name.
|
||||
type orderedObjects []*object
|
||||
|
||||
func (s orderedObjects) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s orderedObjects) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
func (s orderedObjects) Less(i, j int) bool {
|
||||
return s[i].name < s[j].name
|
||||
}
|
||||
|
||||
func (obj *object) Key() Key {
|
||||
return Key{
|
||||
Key: obj.name,
|
||||
LastModified: obj.mtime.Format("2006-01-02T15:04:05"),
|
||||
Size: int64(len(obj.data)),
|
||||
ETag: fmt.Sprintf("%x", obj.checksum),
|
||||
ContentType: obj.content_type,
|
||||
}
|
||||
}
|
||||
|
||||
var metaHeaders = map[string]bool{
|
||||
"Content-Type": true,
|
||||
"Content-Encoding": true,
|
||||
"Content-Disposition": true,
|
||||
"X-Object-Manifest": true,
|
||||
}
|
||||
|
||||
var rangeRegexp = regexp.MustCompile("(bytes=)?([0-9]*)-([0-9]*)")
|
||||
|
||||
// GET on an object gets the contents of the object.
|
||||
func (objr objectResource) get(a *action) interface{} {
|
||||
var (
|
||||
etag []byte
|
||||
reader io.Reader
|
||||
start int
|
||||
end int = -1
|
||||
)
|
||||
obj := objr.object
|
||||
if obj == nil {
|
||||
fatalf(404, "Not Found", "The resource could not be found.")
|
||||
}
|
||||
|
||||
h := a.w.Header()
|
||||
// add metadata
|
||||
obj.getMetadata(a)
|
||||
|
||||
if r := a.req.Header.Get("Range"); r != "" {
|
||||
m := rangeRegexp.FindStringSubmatch(r)
|
||||
if m[2] != "" {
|
||||
start, _ = strconv.Atoi(m[2])
|
||||
}
|
||||
if m[3] != "" {
|
||||
end, _ = strconv.Atoi(m[3])
|
||||
}
|
||||
}
|
||||
|
||||
max := func(a int, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
if manifest, ok := obj.meta["X-Object-Manifest"]; ok {
|
||||
var segments []io.Reader
|
||||
components := strings.SplitN(manifest[0], "/", 2)
|
||||
segContainer := a.user.Containers[components[0]]
|
||||
prefix := components[1]
|
||||
resp := segContainer.list("", "", prefix, "")
|
||||
sum := md5.New()
|
||||
cursor := 0
|
||||
size := 0
|
||||
for _, item := range resp {
|
||||
if obj, ok := item.(*object); ok {
|
||||
length := len(obj.data)
|
||||
size += length
|
||||
sum.Write([]byte(hex.EncodeToString(obj.checksum)))
|
||||
if start >= cursor+length {
|
||||
continue
|
||||
}
|
||||
segments = append(segments, bytes.NewReader(obj.data[max(0, start-cursor):]))
|
||||
cursor += length
|
||||
}
|
||||
}
|
||||
etag = sum.Sum(nil)
|
||||
if end == -1 {
|
||||
end = size
|
||||
}
|
||||
reader = io.LimitReader(io.MultiReader(segments...), int64(end-start))
|
||||
} else {
|
||||
if end == -1 {
|
||||
end = len(obj.data)
|
||||
}
|
||||
etag = obj.checksum
|
||||
reader = bytes.NewReader(obj.data[start:end])
|
||||
}
|
||||
|
||||
h.Set("Content-Length", fmt.Sprint(end-start))
|
||||
h.Set("ETag", hex.EncodeToString(etag))
|
||||
h.Set("Last-Modified", obj.mtime.Format(http.TimeFormat))
|
||||
|
||||
if a.req.Method == "HEAD" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO avoid holding the lock when writing data.
|
||||
_, err := io.Copy(a.w, reader)
|
||||
if err != nil {
|
||||
// we can't do much except just log the fact.
|
||||
log.Printf("error writing data: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PUT on an object creates the object.
|
||||
func (objr objectResource) put(a *action) interface{} {
|
||||
var expectHash []byte
|
||||
if c := a.req.Header.Get("ETag"); c != "" {
|
||||
var err error
|
||||
expectHash, err = hex.DecodeString(c)
|
||||
if err != nil || len(expectHash) != md5.Size {
|
||||
fatalf(400, "InvalidDigest", "The ETag you specified was invalid")
|
||||
}
|
||||
}
|
||||
sum := md5.New()
|
||||
// TODO avoid holding lock while reading data.
|
||||
data, err := ioutil.ReadAll(io.TeeReader(a.req.Body, sum))
|
||||
if err != nil {
|
||||
fatalf(400, "TODO", "read error")
|
||||
}
|
||||
gotHash := sum.Sum(nil)
|
||||
if expectHash != nil && bytes.Compare(gotHash, expectHash) != 0 {
|
||||
fatalf(422, "Bad ETag", "The ETag you specified did not match what we received")
|
||||
}
|
||||
if a.req.ContentLength >= 0 && int64(len(data)) != a.req.ContentLength {
|
||||
fatalf(400, "IncompleteBody", "You did not provide the number of bytes specified by the Content-Length HTTP header")
|
||||
}
|
||||
|
||||
// TODO is this correct, or should we erase all previous metadata?
|
||||
obj := objr.object
|
||||
if obj == nil {
|
||||
obj = &object{
|
||||
name: objr.name,
|
||||
metadata: metadata{
|
||||
meta: make(http.Header),
|
||||
},
|
||||
}
|
||||
a.user.Objects++
|
||||
} else {
|
||||
objr.container.bytes -= len(obj.data)
|
||||
a.user.BytesUsed -= int64(len(obj.data))
|
||||
}
|
||||
|
||||
var content_type string
|
||||
if content_type = a.req.Header.Get("Content-Type"); content_type == "" {
|
||||
content_type = mime.TypeByExtension(obj.name)
|
||||
if content_type == "" {
|
||||
content_type = "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
// PUT request has been successful - save data and metadata
|
||||
obj.setMetadata(a, "object")
|
||||
obj.content_type = content_type
|
||||
obj.data = data
|
||||
obj.checksum = gotHash
|
||||
obj.mtime = time.Now().UTC()
|
||||
objr.container.objects[objr.name] = obj
|
||||
objr.container.bytes += len(data)
|
||||
a.user.BytesUsed += int64(len(data))
|
||||
|
||||
h := a.w.Header()
|
||||
h.Set("ETag", hex.EncodeToString(obj.checksum))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (objr objectResource) delete(a *action) interface{} {
|
||||
if objr.object == nil {
|
||||
fatalf(404, "NoSuchKey", "The specified key does not exist.")
|
||||
}
|
||||
|
||||
objr.container.bytes -= len(objr.object.data)
|
||||
a.user.BytesUsed -= int64(len(objr.object.data))
|
||||
delete(objr.container.objects, objr.name)
|
||||
a.user.Objects--
|
||||
return nil
|
||||
}
|
||||
|
||||
func (objr objectResource) post(a *action) interface{} {
|
||||
obj := objr.object
|
||||
obj.setMetadata(a, "object")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (objr objectResource) copy(a *action) interface{} {
|
||||
if objr.object == nil {
|
||||
fatalf(404, "NoSuchKey", "The specified key does not exist.")
|
||||
}
|
||||
|
||||
obj := objr.object
|
||||
destination := a.req.Header.Get("Destination")
|
||||
if destination == "" {
|
||||
fatalf(400, "Bad Request", "You must provide a Destination header")
|
||||
}
|
||||
|
||||
var (
|
||||
obj2 *object
|
||||
objr2 objectResource
|
||||
)
|
||||
|
||||
destURL, _ := url.Parse("/v1/AUTH_" + TEST_ACCOUNT + "/" + destination)
|
||||
r := a.srv.resourceForURL(destURL)
|
||||
switch t := r.(type) {
|
||||
case objectResource:
|
||||
objr2 = t
|
||||
if objr2.object == nil {
|
||||
obj2 = &object{
|
||||
name: objr2.name,
|
||||
metadata: metadata{
|
||||
meta: make(http.Header),
|
||||
},
|
||||
}
|
||||
a.user.Objects++
|
||||
} else {
|
||||
obj2 = objr2.object
|
||||
objr2.container.bytes -= len(obj2.data)
|
||||
a.user.BytesUsed -= int64(len(obj2.data))
|
||||
}
|
||||
default:
|
||||
fatalf(400, "Bad Request", "Destination must point to a valid object path")
|
||||
}
|
||||
|
||||
obj2.content_type = obj.content_type
|
||||
obj2.data = obj.data
|
||||
obj2.checksum = obj.checksum
|
||||
obj2.mtime = time.Now()
|
||||
objr2.container.objects[objr2.name] = obj2
|
||||
objr2.container.bytes += len(obj.data)
|
||||
a.user.BytesUsed += int64(len(obj.data))
|
||||
|
||||
for key, values := range obj.metadata.meta {
|
||||
obj2.metadata.meta[key] = values
|
||||
}
|
||||
obj2.setMetadata(a, "object")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SwiftServer) serveHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// ignore error from ParseForm as it's usually spurious.
|
||||
req.ParseForm()
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if DEBUG {
|
||||
log.Printf("swifttest %q %q", req.Method, req.URL)
|
||||
}
|
||||
a := &action{
|
||||
srv: s,
|
||||
w: w,
|
||||
req: req,
|
||||
reqId: fmt.Sprintf("%09X", s.reqId),
|
||||
}
|
||||
s.reqId++
|
||||
|
||||
var r resource
|
||||
defer func() {
|
||||
switch err := recover().(type) {
|
||||
case *swiftError:
|
||||
w.Header().Set("Content-Type", `text/plain; charset=utf-8`)
|
||||
http.Error(w, err.Message, err.statusCode)
|
||||
case nil:
|
||||
default:
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
var resp interface{}
|
||||
|
||||
if req.URL.String() == "/v1.0" {
|
||||
username := req.Header.Get("x-auth-user")
|
||||
key := req.Header.Get("x-auth-key")
|
||||
if acct, ok := s.Accounts[username]; ok {
|
||||
if acct.password == key {
|
||||
r := make([]byte, 16)
|
||||
_, _ = rand.Read(r)
|
||||
id := fmt.Sprintf("%X", r)
|
||||
w.Header().Set("X-Storage-Url", s.URL+"/AUTH_"+username)
|
||||
w.Header().Set("X-Auth-Token", "AUTH_tk"+string(id))
|
||||
w.Header().Set("X-Storage-Token", "AUTH_tk"+string(id))
|
||||
s.Sessions[id] = &session{
|
||||
username: username,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
panic(notAuthorized())
|
||||
}
|
||||
|
||||
if req.URL.String() == "/info" {
|
||||
jsonMarshal(w, &swift.SwiftInfo{
|
||||
"swift": map[string]interface{}{
|
||||
"version": "1.2",
|
||||
},
|
||||
"tempurl": map[string]interface{}{
|
||||
"methods": []string{"GET", "HEAD", "PUT"},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
r = s.resourceForURL(req.URL)
|
||||
|
||||
key := req.Header.Get("x-auth-token")
|
||||
signature := req.URL.Query().Get("temp_url_sig")
|
||||
expires := req.URL.Query().Get("temp_url_expires")
|
||||
if key == "" && signature != "" && expires != "" {
|
||||
accountName, _, _, _ := s.parseURL(req.URL)
|
||||
secretKey := ""
|
||||
if account, ok := s.Accounts[accountName]; ok {
|
||||
secretKey = account.meta.Get("X-Account-Meta-Temp-Url-Key")
|
||||
}
|
||||
|
||||
get_hmac := func(method string) string {
|
||||
mac := hmac.New(sha1.New, []byte(secretKey))
|
||||
body := fmt.Sprintf("%s\n%s\n%s", method, expires, req.URL.Path)
|
||||
mac.Write([]byte(body))
|
||||
return hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
if req.Method == "HEAD" {
|
||||
if signature != get_hmac("GET") && signature != get_hmac("POST") && signature != get_hmac("PUT") {
|
||||
panic(notAuthorized())
|
||||
}
|
||||
} else if signature != get_hmac(req.Method) {
|
||||
panic(notAuthorized())
|
||||
}
|
||||
} else {
|
||||
session, ok := s.Sessions[key[7:]]
|
||||
if !ok {
|
||||
panic(notAuthorized())
|
||||
}
|
||||
|
||||
a.user = s.Accounts[session.username]
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case "PUT":
|
||||
resp = r.put(a)
|
||||
case "GET", "HEAD":
|
||||
resp = r.get(a)
|
||||
case "DELETE":
|
||||
resp = r.delete(a)
|
||||
case "POST":
|
||||
resp = r.post(a)
|
||||
case "COPY":
|
||||
resp = r.copy(a)
|
||||
default:
|
||||
fatalf(400, "MethodNotAllowed", "unknown http request method %q", req.Method)
|
||||
}
|
||||
|
||||
content_type := req.Header.Get("Content-Type")
|
||||
if resp != nil && req.Method != "HEAD" {
|
||||
if strings.HasPrefix(content_type, "application/json") ||
|
||||
req.URL.Query().Get("format") == "json" {
|
||||
jsonMarshal(w, resp)
|
||||
} else {
|
||||
switch r := resp.(type) {
|
||||
case string:
|
||||
w.Write([]byte(r))
|
||||
default:
|
||||
w.Write(resp.([]byte))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func jsonMarshal(w io.Writer, x interface{}) {
|
||||
if err := json.NewEncoder(w).Encode(x); err != nil {
|
||||
panic(fmt.Errorf("error marshalling %#v: %v", x, err))
|
||||
}
|
||||
}
|
||||
|
||||
var pathRegexp = regexp.MustCompile("/v1/AUTH_([a-zA-Z0-9]+)(/([^/]+)(/(.*))?)?")
|
||||
|
||||
func (srv *SwiftServer) parseURL(u *url.URL) (account string, container string, object string, err error) {
|
||||
m := pathRegexp.FindStringSubmatch(u.Path)
|
||||
if m == nil {
|
||||
return "", "", "", fmt.Errorf("Couldn't parse the specified URI")
|
||||
}
|
||||
account = m[1]
|
||||
container = m[3]
|
||||
object = m[5]
|
||||
return
|
||||
}
|
||||
|
||||
// resourceForURL returns a resource object for the given URL.
|
||||
func (srv *SwiftServer) resourceForURL(u *url.URL) (r resource) {
|
||||
accountName, containerName, objectName, err := srv.parseURL(u)
|
||||
|
||||
if err != nil {
|
||||
fatalf(404, "InvalidURI", err.Error())
|
||||
}
|
||||
|
||||
account, ok := srv.Accounts[accountName]
|
||||
if !ok {
|
||||
fatalf(404, "NoSuchAccount", "The specified account does not exist")
|
||||
}
|
||||
|
||||
if containerName == "" {
|
||||
return rootResource{}
|
||||
}
|
||||
b := containerResource{
|
||||
name: containerName,
|
||||
container: account.Containers[containerName],
|
||||
}
|
||||
|
||||
if objectName == "" {
|
||||
return b
|
||||
}
|
||||
|
||||
if b.container == nil {
|
||||
fatalf(404, "NoSuchContainer", "The specified container does not exist")
|
||||
}
|
||||
|
||||
objr := objectResource{
|
||||
name: objectName,
|
||||
version: u.Query().Get("versionId"),
|
||||
container: b.container,
|
||||
}
|
||||
|
||||
if obj := objr.container.objects[objr.name]; obj != nil {
|
||||
objr.object = obj
|
||||
}
|
||||
return objr
|
||||
}
|
||||
|
||||
// nullResource has error stubs for all resource methods.
|
||||
type nullResource struct{}
|
||||
|
||||
func notAllowed() interface{} {
|
||||
fatalf(400, "MethodNotAllowed", "The specified method is not allowed against this resource")
|
||||
return nil
|
||||
}
|
||||
|
||||
func notAuthorized() interface{} {
|
||||
fatalf(401, "Unauthorized", "This server could not verify that you are authorized to access the document you requested.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nullResource) put(a *action) interface{} { return notAllowed() }
|
||||
func (nullResource) get(a *action) interface{} { return notAllowed() }
|
||||
func (nullResource) post(a *action) interface{} { return notAllowed() }
|
||||
func (nullResource) delete(a *action) interface{} { return notAllowed() }
|
||||
func (nullResource) copy(a *action) interface{} { return notAllowed() }
|
||||
|
||||
type rootResource struct{}
|
||||
|
||||
func (rootResource) put(a *action) interface{} { return notAllowed() }
|
||||
func (rootResource) get(a *action) interface{} {
|
||||
marker := a.req.Form.Get("marker")
|
||||
prefix := a.req.Form.Get("prefix")
|
||||
format := a.req.URL.Query().Get("format")
|
||||
|
||||
h := a.w.Header()
|
||||
|
||||
h.Set("X-Account-Bytes-Used", strconv.Itoa(int(a.user.BytesUsed)))
|
||||
h.Set("X-Account-Container-Count", strconv.Itoa(int(a.user.Account.Containers)))
|
||||
h.Set("X-Account-Object-Count", strconv.Itoa(int(a.user.Objects)))
|
||||
|
||||
// add metadata
|
||||
a.user.metadata.getMetadata(a)
|
||||
|
||||
if a.req.Method == "HEAD" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var tmp orderedContainers
|
||||
// first get all matching objects and arrange them in alphabetical order.
|
||||
for _, container := range a.user.Containers {
|
||||
if strings.HasPrefix(container.name, prefix) {
|
||||
tmp = append(tmp, container)
|
||||
}
|
||||
}
|
||||
sort.Sort(tmp)
|
||||
|
||||
resp := make([]Folder, 0)
|
||||
for _, container := range tmp {
|
||||
if container.name <= marker {
|
||||
continue
|
||||
}
|
||||
if format == "json" {
|
||||
resp = append(resp, Folder{
|
||||
Count: len(container.objects),
|
||||
Bytes: container.bytes,
|
||||
Name: container.name,
|
||||
})
|
||||
} else {
|
||||
a.w.Write([]byte(container.name + "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
return resp
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r rootResource) post(a *action) interface{} {
|
||||
a.user.metadata.setMetadata(a, "account")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rootResource) delete(a *action) interface{} {
|
||||
if a.req.URL.Query().Get("bulk-delete") == "1" {
|
||||
fatalf(403, "Operation forbidden", "Bulk delete is not supported")
|
||||
}
|
||||
|
||||
return notAllowed()
|
||||
}
|
||||
|
||||
func (rootResource) copy(a *action) interface{} { return notAllowed() }
|
||||
|
||||
func NewSwiftServer(address string) (*SwiftServer, error) {
|
||||
var (
|
||||
l net.Listener
|
||||
err error
|
||||
)
|
||||
if strings.Index(address, ":") == -1 {
|
||||
for port := 1024; port < 65535; port++ {
|
||||
addr := fmt.Sprintf("%s:%d", address, port)
|
||||
if l, err = net.Listen("tcp", addr); err == nil {
|
||||
address = addr
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
l, err = net.Listen("tcp", address)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot listen on %s: %v", address, err)
|
||||
}
|
||||
|
||||
server := &SwiftServer{
|
||||
Listener: l,
|
||||
AuthURL: "http://" + l.Addr().String() + "/v1.0",
|
||||
URL: "http://" + l.Addr().String() + "/v1",
|
||||
Accounts: make(map[string]*account),
|
||||
Sessions: make(map[string]*session),
|
||||
}
|
||||
|
||||
server.Accounts[TEST_ACCOUNT] = &account{
|
||||
password: TEST_ACCOUNT,
|
||||
metadata: metadata{
|
||||
meta: make(http.Header),
|
||||
},
|
||||
Containers: make(map[string]*container),
|
||||
}
|
||||
|
||||
go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
server.serveHTTP(w, req)
|
||||
}))
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (srv *SwiftServer) Close() {
|
||||
srv.Listener.Close()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue