forked from TrueCloudLab/restic
rest: Rework handling HTTP2 zero-length replies bug
Add comment that the check is based on the stdlib HTTP2 client. Refactor the checks into a function. Return an error if the value in the Content-Length header cannot be parsed.
This commit is contained in:
parent
7d28006e2e
commit
d8ea10db8c
1 changed files with 43 additions and 10 deletions
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
|
@ -198,6 +199,44 @@ func (b *Backend) Load(ctx context.Context, h restic.Handle, length int, offset
|
|||
return err
|
||||
}
|
||||
|
||||
// checkContentLength returns an error if the server returned a value in the
|
||||
// Content-Length header in an HTTP2 connection, but closed the connection
|
||||
// before any data was sent.
|
||||
//
|
||||
// This is a workaround for https://github.com/golang/go/issues/46071
|
||||
//
|
||||
// See also https://forum.restic.net/t/http2-stream-closed-connection-reset-context-canceled/3743/10
|
||||
func checkContentLength(resp *http.Response) error {
|
||||
// the following code is based on
|
||||
// https://github.com/golang/go/blob/b7a85e0003cedb1b48a1fd3ae5b746ec6330102e/src/net/http/h2_bundle.go#L8646
|
||||
|
||||
if resp.ContentLength != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if resp.ProtoMajor != 2 && resp.ProtoMinor != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(resp.Header[textproto.CanonicalMIMEHeaderKey("Content-Length")]) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// make sure that if the server returned a content length and we can
|
||||
// parse it, it is really zero, otherwise return an error
|
||||
contentLength := resp.Header.Get("Content-Length")
|
||||
cl, err := strconv.ParseUint(contentLength, 10, 63)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse Content-Length %q: %w", contentLength, err)
|
||||
}
|
||||
|
||||
if cl != 0 {
|
||||
return errors.Errorf("unexpected EOF: got 0 instead of %v bytes", cl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Backend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
debug.Log("Load %v, length %v, offset %v", h, length, offset)
|
||||
if err := h.Valid(); err != nil {
|
||||
|
@ -249,16 +288,10 @@ func (b *Backend) openReader(ctx context.Context, h restic.Handle, length int, o
|
|||
|
||||
// workaround https://github.com/golang/go/issues/46071
|
||||
// see also https://forum.restic.net/t/http2-stream-closed-connection-reset-context-canceled/3743/10
|
||||
if resp.ContentLength == 0 && resp.ProtoMajor == 2 && resp.ProtoMinor == 0 {
|
||||
if clens := resp.Header["Content-Length"]; len(clens) == 1 {
|
||||
if cl, err := strconv.ParseUint(clens[0], 10, 63); err == nil {
|
||||
resp.ContentLength = int64(cl)
|
||||
}
|
||||
if resp.ContentLength != 0 {
|
||||
_ = resp.Body.Close()
|
||||
return nil, errors.Errorf("unexpected EOF got 0 instead of %v bytes", resp.ContentLength)
|
||||
}
|
||||
}
|
||||
err = checkContentLength(resp)
|
||||
if err != nil {
|
||||
_ = resp.Body.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
|
|
Loading…
Reference in a new issue