// Package storagefixture supplies a bounded protocol fixture, not a cloud service. package storagefixture import ( "crypto/md5" "fmt" "io" "net/http" "sync" "time" ) type value struct { data []byte contentType string etag string } func Handler() http.Handler { var mu sync.Mutex values := map[string]value{} return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mu.Lock() defer mu.Unlock() if r.Header.Get("Authorization") == "" && r.URL.Query().Get("X-Amz-Signature") == "" && r.URL.Query().Get("x-oss-signature") == "" { http.Error(w, "signature required", 403) return } key := r.URL.Path switch r.Method { case "PUT": data, err := io.ReadAll(io.LimitReader(r.Body, 1025)) if err != nil || len(data) > 1024 { http.Error(w, "too large", 413) return } v := value{data: data, contentType: r.Header.Get("Content-Type"), etag: fmt.Sprintf("%x", md5.Sum(data))} values[key] = v w.Header().Set("ETag", `"`+v.etag+`"`) w.WriteHeader(200) case "DELETE": delete(values, key) w.WriteHeader(204) case "GET", "HEAD": v, ok := values[key] if !ok { w.Header().Set("Content-Type", "application/xml") w.WriteHeader(404) fmt.Fprint(w, `NoSuchKeynot found`) return } w.Header().Set("Content-Length", fmt.Sprint(len(v.data))) w.Header().Set("Content-Type", v.contentType) w.Header().Set("ETag", `"`+v.etag+`"`) w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) if r.Method == "GET" { w.Write(v.data) } default: http.Error(w, "unsupported", 405) } }) }