func (h *Handler) internalServerError(w http.ResponseWriter, err error) {
	log.Printf("ERROR: %v", err)
	if h.opt.PanicOnError {
		panic(fmt.Sprintf("internal server error: %v", err))
	}
	httpDefaultError(w, http.StatusInternalServerError)
}
internalServerError is called to repot an internal server error.
The error message will be reported in the server logs. If PanicOnError
is set, this will panic instead, which makes debugging easier.
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)
}
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()
}
func (h *Handler) deleteConfig(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("deleteConfig()")
	}
	if h.opt.AppendOnly {
		httpDefaultError(w, http.StatusForbidden)
		return
	}
	cfg := h.getSubPath("config")
	if err := os.Remove(cfg); err != nil {
		if h.opt.Debug {
			log.Print(err)
		}
		if os.IsNotExist(err) {
			httpDefaultError(w, http.StatusNotFound)
		} else {
			h.internalServerError(w, err)
		}
		return
	}
}
const (
	mimeTypeAPIV1 = "application/vnd.x.restic.rest.v1"
	mimeTypeAPIV2 = "application/vnd.x.restic.rest.v2"
)
func (h *Handler) listBlobs(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("listBlobs()")
	}
	switch r.Header.Get("Accept") {
	case mimeTypeAPIV2:
		h.listBlobsV2(w, r)
	default:
		h.listBlobsV1(w, r)
	}
}
func (h *Handler) listBlobsV1(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("listBlobsV1()")
	}
	objectType, _ := h.getObject(r.URL.Path)
	if objectType == "" {
		h.internalServerError(w, fmt.Errorf(
			"cannot determine object type: %s", r.URL.Path))
		return
	}
	path := h.getSubPath(objectType)
	items, err := ioutil.ReadDir(path)
	if err != nil {
		if h.opt.Debug {
			log.Print(err)
		}
		httpDefaultError(w, http.StatusNotFound)
		return
	}
	var names []string
	for _, i := range items {
		if isHashed(objectType) {
			subpath := filepath.Join(path, i.Name())
			var subitems []os.FileInfo
			subitems, err = ioutil.ReadDir(subpath)
			if err != nil {
				if h.opt.Debug {
					log.Print(err)
				}
				httpDefaultError(w, http.StatusNotFound)
				return
			}
			for _, f := range subitems {
				names = append(names, f.Name())
			}
		} else {
			names = append(names, i.Name())
		}
	}
	data, err := json.Marshal(names)
	if err != nil {
		h.internalServerError(w, err)
		return
	}
	w.Header().Set("Content-Type", mimeTypeAPIV1)
	_, _ = w.Write(data)
}
type Blob struct {
	Name string `json:"name"`
	Size int64  `json:"size"`
}
func (h *Handler) listBlobsV2(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("listBlobsV2()")
	}
	objectType, _ := h.getObject(r.URL.Path)
	if objectType == "" {
		h.internalServerError(w, fmt.Errorf(
			"cannot determine object type: %s", r.URL.Path))
		return
	}
	path := h.getSubPath(objectType)
	items, err := ioutil.ReadDir(path)
	if err != nil {
		if h.opt.Debug {
			log.Print(err)
		}
		httpDefaultError(w, http.StatusNotFound)
		return
	}
	var blobs []Blob
	for _, i := range items {
		if isHashed(objectType) {
			subpath := filepath.Join(path, i.Name())
			var subitems []os.FileInfo
			subitems, err = ioutil.ReadDir(subpath)
			if err != nil {
				if h.opt.Debug {
					log.Print(err)
				}
				httpDefaultError(w, http.StatusNotFound)
				return
			}
			for _, f := range subitems {
				blobs = append(blobs, Blob{Name: f.Name(), Size: f.Size()})
			}
		} else {
			blobs = append(blobs, Blob{Name: i.Name(), Size: i.Size()})
		}
	}
	data, err := json.Marshal(blobs)
	if err != nil {
		h.internalServerError(w, err)
		return
	}
	w.Header().Set("Content-Type", mimeTypeAPIV2)
	_, _ = w.Write(data)
}
func (h *Handler) checkBlob(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("checkBlob()")
	}
	objectType, objectID := h.getObject(r.URL.Path)
	if objectType == "" || objectID == "" {
		h.internalServerError(w, fmt.Errorf(
			"cannot determine object type or id: %s", r.URL.Path))
		return
	}
	path := h.getObjectPath(objectType, objectID)
	st, err := os.Stat(path)
	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) getBlob(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("getBlob()")
	}
	objectType, objectID := h.getObject(r.URL.Path)
	if objectType == "" || objectID == "" {
		h.internalServerError(w, fmt.Errorf(
			"cannot determine object type or id: %s", r.URL.Path))
		return
	}
	path := h.getObjectPath(objectType, objectID)
	file, err := os.Open(path)
	if err != nil {
		if h.opt.Debug {
			log.Print(err)
		}
		httpDefaultError(w, http.StatusNotFound)
		return
	}
	wc := datacounter.NewResponseWriterCounter(w)
	http.ServeContent(wc, r, "", time.Unix(0, 0), file)
	if err = file.Close(); err != nil {
		h.internalServerError(w, err)
		return
	}
	h.sendMetric(objectType, BlobRead, wc.Count())
}
func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("saveBlob()")
	}
	objectType, objectID := h.getObject(r.URL.Path)
	if objectType == "" || objectID == "" {
		h.internalServerError(w, fmt.Errorf(
			"cannot determine object type or id: %s", r.URL.Path))
		return
	}
	path := h.getObjectPath(objectType, objectID)
	_, err := os.Stat(path)
	if err == nil {
		httpDefaultError(w, http.StatusForbidden)
		return
	}
	if !os.IsNotExist(err) {
		h.internalServerError(w, err)
		return
	}
	tmpFn := objectID + ".rest-server-temp"
	tf, err := ioutil.TempFile(filepath.Dir(path), tmpFn)
	if os.IsNotExist(err) {
		mkdirErr := os.MkdirAll(filepath.Dir(path), h.opt.DirMode)
		if mkdirErr != nil {
			log.Print(mkdirErr)
		} else {
			tf, err = ioutil.TempFile(filepath.Dir(path), tmpFn)
		}
	}
	if err != nil {
		h.internalServerError(w, err)
		return
	}
	outFile, errCode, err := h.wrapFileWriter(r, tf)
	if err != nil {
		if h.opt.Debug {
			log.Println(err)
		}
		httpDefaultError(w, errCode)
		return
	}
	var written int64
	if h.opt.NoVerifyUpload {
		written, err = io.Copy(outFile, r.Body)
	} else {
		hasher := sha256.New()
		written, err = io.Copy(outFile, io.TeeReader(r.Body, hasher))
		if err == nil && hex.EncodeToString(hasher.Sum(nil)) != objectID {
			err = errFileContentDoesntMatchHash
		}
	}
	if err != nil {
		_ = tf.Close()
		_ = os.Remove(tf.Name())
		h.incrementRepoSpaceUsage(-written)
		if h.opt.Debug {
			log.Print(err)
		}
		var pathError *os.PathError
		if errors.As(err, &pathError) && (pathError.Err == syscall.ENOSPC ||
			pathError.Err == syscall.EDQUOT) {
			httpDefaultError(w, http.StatusInsufficientStorage)
		} else if errors.Is(err, errFileContentDoesntMatchHash) ||
			errors.Is(err, io.ErrUnexpectedEOF) ||
			errors.Is(err, http.ErrMissingBoundary) ||
			errors.Is(err, http.ErrNotMultipart) {
			httpDefaultError(w, http.StatusBadRequest)
		} else {
			h.internalServerError(w, err)
		}
		return
	}
	if err := tf.Sync(); err != nil {
		_ = tf.Close()
		_ = os.Remove(tf.Name())
		h.incrementRepoSpaceUsage(-written)
		h.internalServerError(w, err)
		return
	}
	if err := tf.Close(); err != nil {
		_ = os.Remove(tf.Name())
		h.incrementRepoSpaceUsage(-written)
		h.internalServerError(w, err)
		return
	}
	if err := os.Rename(tf.Name(), path); err != nil {
		_ = os.Remove(tf.Name())
		h.incrementRepoSpaceUsage(-written)
		h.internalServerError(w, err)
		return
	}
	if err := syncDir(filepath.Dir(path)); err != nil {
		h.internalServerError(w, err)
		return
	}
	h.sendMetric(objectType, BlobWrite, uint64(written))
}
func syncDir(dirname string) error {
	if runtime.GOOS == "windows" {
		return nil
	}
	dir, err := os.Open(dirname)
	if err != nil {
		return err
	}
	err = dir.Sync()
	if err != nil {
		_ = dir.Close()
		return err
	}
	return dir.Close()
}
func (h *Handler) deleteBlob(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("deleteBlob()")
	}
	objectType, objectID := h.getObject(r.URL.Path)
	if objectType == "" || objectID == "" {
		h.internalServerError(w, fmt.Errorf(
			"cannot determine object type or id: %s", r.URL.Path))
		return
	}
	if h.opt.AppendOnly && objectType != "locks" {
		httpDefaultError(w, http.StatusForbidden)
		return
	}
	path := h.getObjectPath(objectType, objectID)
	var size int64
	if h.needSize() {
		stat, err := os.Stat(path)
		if err == nil {
			size = stat.Size()
		}
	}
	if err := os.Remove(path); err != nil {
		if h.opt.Debug {
			log.Print(err)
		}
		if os.IsNotExist(err) {
			httpDefaultError(w, http.StatusNotFound)
		} else {
			h.internalServerError(w, err)
		}
		return
	}
	h.incrementRepoSpaceUsage(-size)
	h.sendMetric(objectType, BlobDelete, uint64(size))
}
func (h *Handler) createRepo(w http.ResponseWriter, r *http.Request) {
	if h.opt.Debug {
		log.Println("createRepo()")
	}
	if r.URL.Query().Get("create") != "true" {
		httpDefaultError(w, http.StatusBadRequest)
		return
	}
	log.Printf("Creating repository directories in %s\n", h.path)
	if err := os.MkdirAll(h.path, h.opt.DirMode); err != nil {
		h.internalServerError(w, err)
		return
	}
	for _, d := range ObjectTypes {
		if err := os.Mkdir(filepath.Join(h.path, d), h.opt.DirMode); err != nil && !os.IsExist(err) {
			h.internalServerError(w, err)
			return
		}
	}
	for i := 0; i < 256; i++ {
		dirPath := filepath.Join(h.path, "data", fmt.Sprintf("%02x", i))
		if err := os.Mkdir(dirPath, h.opt.DirMode); err != nil && !os.IsExist(err) {
			h.internalServerError(w, err)
			return
		}
	}
}