serve restic: fix error handling

* serve restic: return internal error if listing failed

If listing a remote failed, then rclone returned http status "not
found". This has become a problem since restic 0.16.0 which ignores "not
found"-errors while listing a directory.

Just return internal server error, if something unexpected happens while
listing a directory.

* serve restic: fix error handling if getting a file fails

If the call to `newObject` in `serveObject` fails, then rclone always
returned a "not found" error. This prevents restic from distinguishing
permanent "not found" errors from everything else.

Thus, only return "not found" if the object is not found and an internal
server error otherwise.
This commit is contained in:
Michael Eischer 2024-01-29 18:54:23 +01:00 committed by GitHub
parent 6e4dd2ab96
commit ef2c5a1998
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 63 additions and 3 deletions

View File

@ -341,7 +341,11 @@ func (s *server) serveObject(w http.ResponseWriter, r *http.Request) {
o, err := s.newObject(r.Context(), remote)
if err != nil {
fs.Debugf(remote, "%s request error: %v", r.Method, err)
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
if errors.Is(err, fs.ErrorObjectNotFound) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
} else {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return
}
serve.Object(w, r, o)
@ -404,7 +408,7 @@ func (s *server) deleteObject(w http.ResponseWriter, r *http.Request) {
if err := o.Remove(r.Context()); err != nil {
fs.Errorf(remote, "Delete request remove error: %v", err)
if err == fs.ErrorObjectNotFound {
if errors.Is(err, fs.ErrorObjectNotFound) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
} else {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -466,7 +470,7 @@ func (s *server) listObjects(w http.ResponseWriter, r *http.Request) {
if err != nil {
if !errors.Is(err, fs.ErrorDirNotFound) {
fs.Errorf(remote, "list failed: %#v %T", err, err)
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
}

View File

@ -5,6 +5,7 @@ package restic
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"os"
@ -12,6 +13,8 @@ import (
"testing"
_ "github.com/rclone/rclone/backend/all"
"github.com/rclone/rclone/cmd"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fstest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -114,3 +117,56 @@ func TestMakeRemote(t *testing.T) {
got.ServeHTTP(w, r)
}
}
type listErrorFs struct {
fs.Fs
}
func (f *listErrorFs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
return fs.DirEntries{}, errors.New("oops")
}
func TestListErrors(t *testing.T) {
ctx := context.Background()
// setup rclone with a local backend in a temporary directory
tempdir := t.TempDir()
opt := newOpt()
// make a new file system in the temp dir
f := &listErrorFs{Fs: cmd.NewFsSrc([]string{tempdir})}
s, err := newServer(ctx, f, &opt)
require.NoError(t, err)
router := s.Server.Router()
req := newRequest(t, "GET", "/test/snapshots/", nil)
checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusInternalServerError)})
}
type newObjectErrorFs struct {
fs.Fs
err error
}
func (f *newObjectErrorFs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
return nil, f.err
}
func TestServeErrors(t *testing.T) {
ctx := context.Background()
// setup rclone with a local backend in a temporary directory
tempdir := t.TempDir()
opt := newOpt()
// make a new file system in the temp dir
f := &newObjectErrorFs{Fs: cmd.NewFsSrc([]string{tempdir})}
s, err := newServer(ctx, f, &opt)
require.NoError(t, err)
router := s.Server.Router()
f.err = errors.New("oops")
req := newRequest(t, "GET", "/test/config", nil)
checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusInternalServerError)})
f.err = fs.ErrorObjectNotFound
checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusNotFound)})
}