simple backend

This commit is contained in:
Chapuis Bertil 2015-09-16 23:34:11 +02:00
parent 016bbf619a
commit c19c63325c
10 changed files with 123 additions and 668 deletions

31
auth.go
View file

@ -1,31 +0,0 @@
package main
import (
"errors"
"net/http"
"path/filepath"
)
func Authorize(r *http.Request, c *Context) error {
file := filepath.Join(c.path, ".htpasswd")
htpasswd, err := NewHtpasswdFromFile(file)
if err != nil {
return errors.New("internal server error")
}
username, password, ok := r.BasicAuth()
if !ok {
return errors.New("malformed basic auth credentials")
}
if !htpasswd.Validate(username, password) {
return errors.New("unknown user")
}
repo, err := RepositoryName(r.RequestURI)
if err != nil || repo != username {
return errors.New("wrong repository")
}
return nil
}

View file

@ -1,35 +0,0 @@
package main
import (
"os"
"path/filepath"
"github.com/restic/restic/backend"
)
// A Context specifies the root directory where all repositories are stored
type Context struct {
path string
}
func NewContext(path string) Context {
path = filepath.Clean(path)
if _, err := os.Stat(path); err != nil {
os.MkdirAll(path, backend.Modes.Dir)
}
return Context{path}
}
func (c *Context) Repository(name string) (*Repository, error) {
name, err := ParseRepositoryName(name)
if err != nil {
return nil, err
}
repo, err := NewRepository(filepath.Join(c.path, name))
if err != nil {
return nil, err
}
return repo, nil
}

View file

@ -1,224 +0,0 @@
package main
import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
)
type Handler func(w http.ResponseWriter, r *http.Request, c *Context)
func HeadConfig(w http.ResponseWriter, r *http.Request, c *Context) {
uri := r.RequestURI
name, err := RepositoryName(uri)
if err != nil {
http.NotFound(w, r)
return
}
repo, err := c.Repository(name)
if err != nil {
http.NotFound(w, r)
return
}
if !repo.HasConfig() {
http.NotFound(w, r)
return
}
}
func GetConfig(w http.ResponseWriter, r *http.Request, c *Context) {
uri := r.RequestURI
name, err := RepositoryName(uri)
if err != nil {
http.NotFound(w, r)
return
}
repo, err := c.Repository(name)
if err != nil {
http.NotFound(w, r)
return
}
config, err := repo.ReadConfig()
if err != nil {
http.NotFound(w, r)
return
}
w.Write(config)
}
func PostConfig(w http.ResponseWriter, r *http.Request, c *Context) {
uri := r.RequestURI
name, err := RepositoryName(uri)
if err != nil {
http.NotFound(w, r)
return
}
repo, err := c.Repository(name)
if err != nil {
http.NotFound(w, r)
return
}
config, err := ioutil.ReadAll(r.Body)
if err != nil {
http.NotFound(w, r)
return
}
errc := repo.WriteConfig(config)
if errc != nil {
http.NotFound(w, r)
return
}
}
func ListBlob(w http.ResponseWriter, r *http.Request, c *Context) {
uri := r.RequestURI
name, err := RepositoryName(uri)
if err != nil {
http.NotFound(w, r)
return
}
repo, err := c.Repository(name)
if err != nil {
http.NotFound(w, r)
return
}
bt := BackendType(uri)
if string(bt) == "" {
http.NotFound(w, r)
return
}
blobs, err := repo.ListBlob(bt)
if err != nil {
http.NotFound(w, r)
return
}
json, err := json.Marshal(blobs)
if err != nil {
http.NotFound(w, r)
return
}
w.Write(json)
}
func HeadBlob(w http.ResponseWriter, r *http.Request, c *Context) {
uri := r.RequestURI
name, err := RepositoryName(uri)
if err != nil {
http.NotFound(w, r)
return
}
repo, err := c.Repository(name)
if err != nil {
http.NotFound(w, r)
return
}
bt := BackendType(uri)
if string(bt) == "" {
http.NotFound(w, r)
return
}
id := BlobID(uri)
if id.IsNull() {
http.NotFound(w, r)
return
}
if !repo.HasBlob(bt, id) {
http.NotFound(w, r)
return
}
}
func GetBlob(w http.ResponseWriter, r *http.Request, c *Context) {
uri := r.RequestURI
name, err := RepositoryName(uri)
if err != nil {
http.NotFound(w, r)
return
}
repo, err := c.Repository(name)
if err != nil {
http.NotFound(w, r)
return
}
bt := BackendType(uri)
if string(bt) == "" {
http.NotFound(w, r)
return
}
id := BlobID(uri)
if id.IsNull() {
http.NotFound(w, r)
return
}
blob, err := repo.ReadBlob(bt, id)
if err != nil {
http.NotFound(w, r)
return
}
http.ServeContent(w, r, "", time.Unix(0, 0), blob)
}
func PostBlob(w http.ResponseWriter, r *http.Request, c *Context) {
uri := r.RequestURI
name, err := RepositoryName(uri)
if err != nil {
http.NotFound(w, r)
return
}
repo, err := c.Repository(name)
if err != nil {
http.NotFound(w, r)
return
}
bt := BackendType(uri)
if string(bt) == "" {
http.NotFound(w, r)
return
}
id := BlobID(uri)
if id.IsNull() {
http.NotFound(w, r)
return
}
blob, err := ioutil.ReadAll(r.Body)
if err != nil {
http.NotFound(w, r)
return
}
errw := repo.WriteBlob(bt, id, blob)
if errw != nil {
http.NotFound(w, r)
return
}
w.WriteHeader(200)
}
func DeleteBlob(w http.ResponseWriter, r *http.Request, c *Context) {
uri := r.RequestURI
name, err := RepositoryName(uri)
if err != nil {
http.NotFound(w, r)
return
}
repo, err := c.Repository(name)
if err != nil {
http.NotFound(w, r)
return
}
bt := BackendType(uri)
if string(bt) == "" {
http.NotFound(w, r)
return
}
id := BlobID(uri)
if id.IsNull() {
http.NotFound(w, r)
return
}
errd := repo.DeleteBlob(bt, id)
if errd != nil {
http.NotFound(w, r)
return
}
}

View file

@ -1,62 +0,0 @@
package main
// Copied from github.com/bitly/oauth2_proxy
import (
"crypto/sha1"
"encoding/base64"
"encoding/csv"
"io"
"log"
"os"
)
// lookup passwords in a htpasswd file
// The entries must have been created with -s for SHA encryption
type HtpasswdFile struct {
Users map[string]string
}
func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
r, err := os.Open(path)
if err != nil {
return nil, err
}
defer r.Close()
return NewHtpasswd(r)
}
func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
csv_reader := csv.NewReader(file)
csv_reader.Comma = ':'
csv_reader.Comment = '#'
csv_reader.TrimLeadingSpace = true
records, err := csv_reader.ReadAll()
if err != nil {
return nil, err
}
h := &HtpasswdFile{Users: make(map[string]string)}
for _, record := range records {
h.Users[record[0]] = record[1]
}
return h, nil
}
func (h *HtpasswdFile) Validate(user string, password string) bool {
realPassword, exists := h.Users[user]
if !exists {
return false
}
if realPassword[:5] == "{SHA}" {
d := sha1.New()
d.Write([]byte(password))
if realPassword[5:] == base64.StdEncoding.EncodeToString(d.Sum(nil)) {
return true
}
} else {
log.Printf("Invalid htpasswd entry for %s. Must be a SHA entry.", user)
}
return false
}

View file

@ -1,96 +0,0 @@
package main
import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/restic/restic/backend"
)
// A Repository is the place where backups are stored
type Repository struct {
path string
}
func NewRepository(path string) (*Repository, error) {
dirs := []string{
path,
filepath.Join(path, string(backend.Data)),
filepath.Join(path, string(backend.Snapshot)),
filepath.Join(path, string(backend.Index)),
filepath.Join(path, string(backend.Lock)),
filepath.Join(path, string(backend.Key)),
}
for _, d := range dirs {
_, err := os.Stat(d)
if err != nil {
err := os.MkdirAll(d, backend.Modes.Dir)
if err != nil {
return nil, err
}
}
}
return &Repository{path}, nil
}
func (r *Repository) HasConfig() bool {
file := filepath.Join(r.path, string(backend.Config))
if _, err := os.Stat(file); err != nil {
return false
}
return true
}
func (r *Repository) ReadConfig() ([]byte, error) {
file := filepath.Join(r.path, string(backend.Config))
return ioutil.ReadFile(file)
}
func (r *Repository) WriteConfig(data []byte) error {
file := filepath.Join(r.path, string(backend.Config))
return ioutil.WriteFile(file, data, backend.Modes.File)
}
func (r *Repository) ListBlob(t backend.Type) ([]string, error) {
var blobs []string
dir := filepath.Join(r.path, string(t))
files, err := ioutil.ReadDir(dir)
if err != nil {
return blobs, err
}
blobs = make([]string, len(files))
for i, f := range files {
blobs[i] = f.Name()
}
return blobs, nil
}
func (r *Repository) HasBlob(bt backend.Type, id backend.ID) bool {
file := filepath.Join(r.path, string(bt), id.String())
if _, err := os.Stat(file); err != nil {
return false
}
return true
}
func (r *Repository) ReadBlob(bt backend.Type, id backend.ID) (io.ReadSeeker, error) {
file := filepath.Join(r.path, string(bt), id.String())
blob, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
return bytes.NewReader(blob), nil
}
func (r *Repository) WriteBlob(bt backend.Type, id backend.ID, data []byte) error {
file := filepath.Join(r.path, string(bt), id.String())
return ioutil.WriteFile(file, data, backend.Modes.File)
}
func (r *Repository) DeleteBlob(bt backend.Type, id backend.ID) error {
file := filepath.Join(r.path, string(bt), id.String())
return os.Remove(file)
}

View file

@ -1,56 +0,0 @@
package main
import (
"io/ioutil"
"os"
"testing"
"github.com/restic/restic/backend"
"github.com/stretchr/testify/require"
)
func TestRepositoryConfig(t *testing.T) {
path := "/tmp/repository"
repository, _ := NewRepository(path)
defer os.RemoveAll(path)
_, e1 := os.Stat(path)
require.NoError(t, e1, "repository not created")
require.False(t, repository.HasConfig())
_, e2 := repository.ReadConfig()
require.Error(t, e2, "reading config should fail")
e3 := repository.WriteConfig([]byte("test"))
require.NoError(t, e3, "writing config should succeed")
require.True(t, repository.HasConfig())
config, _ := repository.ReadConfig()
require.Equal(t, config, []byte("test"), "reading config should succeed")
}
func TestRepositoryBlob(t *testing.T) {
path := "/tmp/repository"
repository, _ := NewRepository(path)
//defer os.RemoveAll(path)
_, e1 := os.Stat(path)
require.NoError(t, e1, "repository not created")
require.False(t, repository.HasBlob(backend.Data, BlobID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")))
_, e2 := repository.ReadBlob(backend.Data, BlobID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
require.Error(t, e2, "reading blob should fail")
e3 := repository.WriteBlob(backend.Data, BlobID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), []byte("test"))
require.NoError(t, e3, "saving blob should succeed")
require.True(t, repository.HasBlob(backend.Data, BlobID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")))
blob, _ := repository.ReadBlob(backend.Data, BlobID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
bytes, e4 := ioutil.ReadAll(blob)
require.NoError(t, e4, e4.Error())
require.Equal(t, bytes, []byte("test"), "reading blob should succeed")
}

View file

@ -1,79 +0,0 @@
package main
import (
"log"
"net/http"
"strings"
"github.com/restic/restic/backend"
)
type Router struct {
Context
}
func NewRouter(context Context) *Router {
return &Router{context}
}
func (router Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
m := r.Method
u := r.RequestURI
log.Printf("%s %s", m, u)
if err := Authorize(r, &router.Context); err == nil {
if handler := RestAPI(m, u); handler != nil {
handler(w, r, &router.Context)
} else {
http.Error(w, "not found", 404)
}
} else {
http.Error(w, err.Error(), 403)
}
}
// The Rest API returns a Handler when a match occur or nil.
func RestAPI(m string, u string) Handler {
s := strings.Split(u, "/")
// Check for valid repository name
_, err := RepositoryName(u)
if err != nil {
return nil
}
// Route config requests
bt := BackendType(u)
if len(s) == 3 && bt == backend.Config {
switch m {
case "HEAD":
return HeadConfig
case "GET":
return GetConfig
case "POST":
return PostConfig
}
}
// Route blob requests
id := BlobID(u)
if len(s) == 4 && string(bt) != "" && bt != backend.Config {
if s[3] == "" && m == "GET" {
return ListBlob
} else if !id.IsNull() {
switch m {
case "HEAD":
return HeadBlob
case "GET":
return GetBlob
case "POST":
return PostBlob
case "DELETE":
return DeleteBlob
}
}
}
return nil
}

View file

@ -119,26 +119,26 @@ func TestRestAPI(t *testing.T) {
route{"GET", "/repo/data/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"GET", "/repo/data/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"POST", "/repo/data/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"POST", "/repo/data/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"DELETE", "/repo/data/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"DELETE", "/repo/data/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"GET", "/repo/snapshot/"}, route{"GET", "/repo/snapshots/"},
route{"HEAD", "/repo/snapshot/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"HEAD", "/repo/snapshots/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"GET", "/repo/snapshot/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"GET", "/repo/snapshots/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"POST", "/repo/snapshot/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"POST", "/repo/snapshots/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"DELETE", "/repo/snapshot/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"DELETE", "/repo/snapshots/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"GET", "/repo/index/"}, route{"GET", "/repo/index/"},
route{"HEAD", "/repo/index/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"HEAD", "/repo/index/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"GET", "/repo/index/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"GET", "/repo/index/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"POST", "/repo/index/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"POST", "/repo/index/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"DELETE", "/repo/index/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"DELETE", "/repo/index/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"GET", "/repo/snapshot/"}, route{"GET", "/repo/locks/"},
route{"HEAD", "/repo/lock/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"HEAD", "/repo/locks/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"GET", "/repo/lock/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"GET", "/repo/locks/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"POST", "/repo/lock/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"POST", "/repo/locks/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"DELETE", "/repo/lock/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"DELETE", "/repo/locks/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"GET", "/repo/key/"}, route{"GET", "/repo/keys/"},
route{"HEAD", "/repo/key/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"HEAD", "/repo/keys/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"GET", "/repo/key/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"GET", "/repo/keys/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"POST", "/repo/key/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"POST", "/repo/keys/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
route{"DELETE", "/repo/key/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, route{"DELETE", "/repo/keys/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
} }
for _, route := range validEndpoints { for _, route := range validEndpoints {

110
server.go
View file

@ -1,10 +1,17 @@
package main package main
import ( import (
"encoding/json"
"flag" "flag"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os"
"path/filepath" "path/filepath"
"strings"
"time"
"github.com/restic/restic/backend"
) )
const ( const (
@ -13,12 +20,111 @@ const (
) )
func main() { func main() {
// Parse command-line args
var path = flag.String("path", "/tmp/restic", "specifies the path of the data directory") var path = flag.String("path", "/tmp/restic", "specifies the path of the data directory")
var tls = flag.Bool("tls", false, "turns on tls support") var tls = flag.Bool("tls", false, "turns on tls support")
flag.Parse() flag.Parse()
context := NewContext(*path) // Create all the necessary subdirectories
router := Router{context} dirs := []string{
backend.Paths.Data,
backend.Paths.Snapshots,
backend.Paths.Index,
backend.Paths.Locks,
backend.Paths.Keys,
}
for _, d := range dirs {
os.MkdirAll(filepath.Join(*path, d), backend.Modes.Dir)
}
router := http.NewServeMux()
// Check if a configuration exists.
router.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
uri := r.RequestURI
method := r.Method
log.Printf("%s %s", method, uri)
file := filepath.Join(*path, "config")
_, err := os.Stat(file)
// Check if the config exists
if method == "HEAD" && err == nil {
return
}
// Get the config
if method == "GET" && err == nil {
bytes, _ := ioutil.ReadFile(file)
w.Write(bytes)
return
}
// Save the config
if method == "POST" && err != nil {
bytes, _ := ioutil.ReadAll(r.Body)
ioutil.WriteFile(file, bytes, 0600)
return
}
http.Error(w, "404 not found", 404)
})
for _, dir := range dirs {
router.HandleFunc("/"+dir+"/", func(w http.ResponseWriter, r *http.Request) {
uri := r.RequestURI
method := r.Method
log.Printf("%s %s", method, uri)
vars := strings.Split(r.RequestURI, "/")
dir := vars[1]
name := vars[2]
path := filepath.Join(*path, dir, name)
_, err := os.Stat(path)
// List the blobs of a given dir.
if method == "GET" && name == "" && err == nil {
files, _ := ioutil.ReadDir(path)
names := make([]string, len(files))
for i, f := range files {
names[i] = f.Name()
}
data, _ := json.Marshal(names)
w.Write(data)
return
}
// Check if the blob esists
if method == "HEAD" && name != "" && err == nil {
return
}
// Get a blob of a given dir.
if method == "GET" && name != "" && err == nil {
file, _ := os.Open(path)
defer file.Close()
http.ServeContent(w, r, "", time.Unix(0, 0), file)
return
}
// Save a blob
if method == "POST" && name != "" && err != nil {
bytes, _ := ioutil.ReadAll(r.Body)
ioutil.WriteFile(path, bytes, 0600)
return
}
// Delete a blob
if method == "DELETE" && name != "" && err == nil {
os.Remove(path)
return
}
http.Error(w, "404 not found", 404)
})
}
// start the server
if !*tls { if !*tls {
log.Printf("start server on port %s", HTTP) log.Printf("start server on port %s", HTTP)
http.ListenAndServe(HTTP, router) http.ListenAndServe(HTTP, router)

View file

@ -1,68 +0,0 @@
package main
import (
"errors"
"regexp"
"strings"
"github.com/restic/restic/backend"
)
// Returns the repository name for a given path
func RepositoryName(u string) (string, error) {
s := strings.Split(u, "/")
if len(s) <= 1 {
return "", errors.New("path does not contain repository name")
}
return ParseRepositoryName(s[1])
}
func ParseRepositoryName(n string) (string, error) {
if len(n) < 1 {
return "", errors.New("repository name should contain at least 1 character")
}
match, err := regexp.MatchString("^[a-zA-Z0-9_-]*$", n)
if !match || err != nil {
return "", errors.New("repository name should not contains special characters")
}
return n, nil
}
// Returns the backend type for a given path
func BackendType(u string) backend.Type {
s := strings.Split(u, "/")
var bt backend.Type
if len(s) > 2 {
bt = parseBackendType(s[2])
}
return bt
}
func parseBackendType(u string) backend.Type {
switch u {
case string(backend.Config):
return backend.Config
case string(backend.Data):
return backend.Data
case string(backend.Snapshot):
return backend.Snapshot
case string(backend.Key):
return backend.Key
case string(backend.Index):
return backend.Index
case string(backend.Lock):
return backend.Lock
default:
return ""
}
}
// Returns the blob ID for a given path
func BlobID(u string) backend.ID {
s := strings.Split(u, "/")
var id backend.ID
if len(s) > 3 {
id, _ = backend.ParseID(s[3])
}
return id
}