func (h *Handler) saveConfig(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("saveConfig()")
	}
	cfg := h.getSubPath("config")
	f, err := os.OpenFile(cfg, os.O_CREATE|os.O_WRONLY|os.O_EXCL, h.opt.FileMode)
	if err != nil && os.IsExist(err) {
		if h.opt.Debug {
			log.Print(err)
		}
		httpDefaultError(w, http.StatusForbidden)
		return
	}
	_, err = io.Copy(f, r.Body)
	if err != nil {
		h.internalServerError(w, err)
		return
	}
	err = f.Close()
	if err != nil {
		h.internalServerError(w, err)
		return
	}
	_ = r.Body.Close()
}
saveConfig allows for a config to be saved.
package repo
import (
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"syscall"
	"time"
	"github.com/minio/sha256-simd"
	"github.com/miolini/datacounter"
	"github.com/restic/rest-server/quota"
)
type Options struct {
	AppendOnly     bool  
	Debug          bool
	DirMode        os.FileMode
	FileMode       os.FileMode
	NoVerifyUpload bool
	PanicOnError bool
	BlobMetricFunc BlobMetricFunc
	QuotaManager   *quota.Manager
}
const DefaultDirMode os.FileMode = 0700
const DefaultFileMode os.FileMode = 0600
func New(path string, opt Options) (*Handler, error) {
	if path == "" {
		return nil, fmt.Errorf("path is required")
	}
	if opt.DirMode == 0 {
		opt.DirMode = DefaultDirMode
	}
	if opt.FileMode == 0 {
		opt.FileMode = DefaultFileMode
	}
	h := Handler{
		path: path,
		opt:  opt,
	}
	return &h, nil
}
type Handler struct {
	path string  
	opt  Options
}
func httpDefaultError(w http.ResponseWriter, code int) {
	http.Error(w, http.StatusText(code), code)
}
func httpMethodNotAllowed(w http.ResponseWriter, allowed []string) {
	w.Header().Set("Allow", strings.Join(allowed, ", "))
	httpDefaultError(w, http.StatusMethodNotAllowed)
}
var errFileContentDoesntMatchHash = errors.New("file content does not match hash")
var BlobPathRE = regexp.MustCompile(`^/(data|index|keys|locks|snapshots)/([0-9a-f]{64})?$`)
var ObjectTypes = []string{"data", "index", "keys", "locks", "snapshots"}
var FileTypes = []string{"config"}
func isHashed(objectType string) bool {
	return objectType == "data"
}
type BlobOperation byte
const (
	BlobRead   = 'R'  
	BlobWrite  = 'W'  
	BlobDelete = 'D'  
)
type BlobMetricFunc func(objectType string, operation BlobOperation, nBytes uint64)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	urlPath := r.URL.Path
	if urlPath == "/" {
		switch r.Method {
		case "POST":
			h.createRepo(w, r)
		default:
			httpMethodNotAllowed(w, []string{"POST"})
		}
		return
	} else if urlPath == "/config" {
		switch r.Method {
		case "HEAD":
			h.checkConfig(w, r)
		case "GET":
			h.getConfig(w, r)
		case "POST":
			h.saveConfig(w, r)
		case "DELETE":
			h.deleteConfig(w, r)
		default:
			httpMethodNotAllowed(w, []string{"HEAD", "GET", "POST", "DELETE"})
		}
		return
	} else if objectType, objectID := h.getObject(urlPath); objectType != "" {
		if objectID == "" {
			switch r.Method {
			case "GET":
				h.listBlobs(w, r)
			default:
				httpMethodNotAllowed(w, []string{"GET"})
			}
			return
		}
		switch r.Method {
		case "HEAD":
			h.checkBlob(w, r)
		case "GET":
			h.getBlob(w, r)
		case "POST":
			h.saveBlob(w, r)
		case "DELETE":
			h.deleteBlob(w, r)
		default:
			httpMethodNotAllowed(w, []string{"HEAD", "GET", "POST", "DELETE"})
		}
		return
	}
	httpDefaultError(w, http.StatusNotFound)
}
func (h *Handler) getObject(urlPath string) (objectType, objectID string) {
	m := BlobPathRE.FindStringSubmatch(urlPath)
	if len(m) == 0 {
		return "", ""  
	}
	if len(m) == 2 || m[2] == "" {
		return m[1], ""  
	}
	return m[1], m[2]
}
func (h *Handler) getSubPath(name string) string {
	return filepath.Join(h.path, name)
}
func (h *Handler) getObjectPath(objectType, objectID string) string {
	if objectType == "" || objectID == "" {
		panic("invalid objectType or objectID")
	}
	if isHashed(objectType) {
		if len(objectID) < 2 {
			panic("getObjectPath: objectID shorter than 2 chars")
		}
		return filepath.Join(h.path, objectType, objectID[:2], objectID)
	}
	return filepath.Join(h.path, objectType, objectID)
}
func (h *Handler) sendMetric(objectType string, operation BlobOperation, nBytes uint64) {
	if f := h.opt.BlobMetricFunc; f != nil {
		f(objectType, operation, nBytes)
	}
}
func (h *Handler) needSize() bool {
	return h.opt.BlobMetricFunc != nil || h.opt.QuotaManager != nil
}
func (h *Handler) incrementRepoSpaceUsage(by int64) {
	if h.opt.QuotaManager != nil {
		h.opt.QuotaManager.IncUsage(by)
	}
}
func (h *Handler) wrapFileWriter(r *http.Request, w io.Writer) (io.Writer, int, error) {
	if h.opt.QuotaManager == nil {
		return w, 0, nil  
	}
	return h.opt.QuotaManager.WrapWriter(r, w)
}
func (h *Handler) checkConfig(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("checkConfig()")
	}
	cfg := h.getSubPath("config")
	st, err := os.Stat(cfg)
	if err != nil {
		if h.opt.Debug {
			log.Print(err)
		}
		httpDefaultError(w, http.StatusNotFound)
		return
	}
	w.Header().Add("Content-Length", fmt.Sprint(st.Size()))
}
func (h *Handler) getConfig(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("getConfig()")
	}
	cfg := h.getSubPath("config")
	bytes, err := ioutil.ReadFile(cfg)
	if err != nil {
		if h.opt.Debug {
			log.Print(err)
		}
		httpDefaultError(w, http.StatusNotFound)
		return
	}
	_, _ = w.Write(bytes)
}