1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2025-01-02 07:26:24 -05:00

Fix DoS attack vulnerability in VMess Option Processing

This commit is contained in:
Shelikhoo 2022-06-12 23:29:09 +01:00
parent 4e24784082
commit 5eff77c48a
No known key found for this signature in database
GPG Key ID: C4D5E79D22B25316
4 changed files with 72 additions and 46 deletions

View File

@ -137,31 +137,35 @@ func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writ
return nil return nil
} }
func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, writer io.Writer) buf.Writer { func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) {
var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{} var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{}
if request.Option.Has(protocol.RequestOptionChunkMasking) { if request.Option.Has(protocol.RequestOptionChunkMasking) {
sizeParser = NewShakeSizeParser(c.requestBodyIV[:]) sizeParser = NewShakeSizeParser(c.requestBodyIV[:])
} }
var padding crypto.PaddingLengthGenerator var padding crypto.PaddingLengthGenerator
if request.Option.Has(protocol.RequestOptionGlobalPadding) { if request.Option.Has(protocol.RequestOptionGlobalPadding) {
padding = sizeParser.(crypto.PaddingLengthGenerator) var ok bool
padding, ok = sizeParser.(crypto.PaddingLengthGenerator)
if !ok {
return nil, newError("invalid option: RequestOptionGlobalPadding")
}
} }
switch request.Security { switch request.Security {
case protocol.SecurityType_NONE: case protocol.SecurityType_NONE:
if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Option.Has(protocol.RequestOptionChunkStream) {
if request.Command.TransferType() == protocol.TransferTypeStream { if request.Command.TransferType() == protocol.TransferTypeStream {
return crypto.NewChunkStreamWriter(sizeParser, writer) return crypto.NewChunkStreamWriter(sizeParser, writer), nil
} }
auth := &crypto.AEADAuthenticator{ auth := &crypto.AEADAuthenticator{
AEAD: new(NoOpAuthenticator), AEAD: new(NoOpAuthenticator),
NonceGenerator: crypto.GenerateEmptyBytes(), NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
} }
return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding) return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding), nil
} }
return buf.NewWriter(writer) return buf.NewWriter(writer), nil
case protocol.SecurityType_LEGACY: case protocol.SecurityType_LEGACY:
aesStream := crypto.NewAesEncryptionStream(c.requestBodyKey[:], c.requestBodyIV[:]) aesStream := crypto.NewAesEncryptionStream(c.requestBodyKey[:], c.requestBodyIV[:])
cryptionWriter := crypto.NewCryptionWriter(aesStream, writer) cryptionWriter := crypto.NewCryptionWriter(aesStream, writer)
@ -171,10 +175,10 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write
NonceGenerator: crypto.GenerateEmptyBytes(), NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
} }
return crypto.NewAuthenticationWriter(auth, sizeParser, cryptionWriter, request.Command.TransferType(), padding) return crypto.NewAuthenticationWriter(auth, sizeParser, cryptionWriter, request.Command.TransferType(), padding), nil
} }
return &buf.SequentialWriter{Writer: cryptionWriter} return &buf.SequentialWriter{Writer: cryptionWriter}, nil
case protocol.SecurityType_AES128_GCM: case protocol.SecurityType_AES128_GCM:
aead := crypto.NewAesGcm(c.requestBodyKey[:]) aead := crypto.NewAesGcm(c.requestBodyKey[:])
auth := &crypto.AEADAuthenticator{ auth := &crypto.AEADAuthenticator{
@ -193,7 +197,7 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write
} }
sizeParser = NewAEADSizeParser(lengthAuth) sizeParser = NewAEADSizeParser(lengthAuth)
} }
return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil
case protocol.SecurityType_CHACHA20_POLY1305: case protocol.SecurityType_CHACHA20_POLY1305:
aead, err := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.requestBodyKey[:])) aead, err := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.requestBodyKey[:]))
common.Must(err) common.Must(err)
@ -215,9 +219,9 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write
} }
sizeParser = NewAEADSizeParser(lengthAuth) sizeParser = NewAEADSizeParser(lengthAuth)
} }
return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil
default: default:
panic("Unknown security type.") return nil, newError("invalid option: Security")
} }
} }
@ -306,21 +310,25 @@ func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.Respon
return header, nil return header, nil
} }
func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, reader io.Reader) buf.Reader { func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, reader io.Reader) (buf.Reader, error) {
var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{} var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{}
if request.Option.Has(protocol.RequestOptionChunkMasking) { if request.Option.Has(protocol.RequestOptionChunkMasking) {
sizeParser = NewShakeSizeParser(c.responseBodyIV[:]) sizeParser = NewShakeSizeParser(c.responseBodyIV[:])
} }
var padding crypto.PaddingLengthGenerator var padding crypto.PaddingLengthGenerator
if request.Option.Has(protocol.RequestOptionGlobalPadding) { if request.Option.Has(protocol.RequestOptionGlobalPadding) {
padding = sizeParser.(crypto.PaddingLengthGenerator) var ok bool
padding, ok = sizeParser.(crypto.PaddingLengthGenerator)
if !ok {
return nil, newError("invalid option: RequestOptionGlobalPadding")
}
} }
switch request.Security { switch request.Security {
case protocol.SecurityType_NONE: case protocol.SecurityType_NONE:
if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Option.Has(protocol.RequestOptionChunkStream) {
if request.Command.TransferType() == protocol.TransferTypeStream { if request.Command.TransferType() == protocol.TransferTypeStream {
return crypto.NewChunkStreamReader(sizeParser, reader) return crypto.NewChunkStreamReader(sizeParser, reader), nil
} }
auth := &crypto.AEADAuthenticator{ auth := &crypto.AEADAuthenticator{
@ -329,10 +337,10 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read
AdditionalDataGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
} }
return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding) return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding), nil
} }
return buf.NewReader(reader) return buf.NewReader(reader), nil
case protocol.SecurityType_LEGACY: case protocol.SecurityType_LEGACY:
if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Option.Has(protocol.RequestOptionChunkStream) {
auth := &crypto.AEADAuthenticator{ auth := &crypto.AEADAuthenticator{
@ -340,10 +348,10 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read
NonceGenerator: crypto.GenerateEmptyBytes(), NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
} }
return crypto.NewAuthenticationReader(auth, sizeParser, c.responseReader, request.Command.TransferType(), padding) return crypto.NewAuthenticationReader(auth, sizeParser, c.responseReader, request.Command.TransferType(), padding), nil
} }
return buf.NewReader(c.responseReader) return buf.NewReader(c.responseReader), nil
case protocol.SecurityType_AES128_GCM: case protocol.SecurityType_AES128_GCM:
aead := crypto.NewAesGcm(c.responseBodyKey[:]) aead := crypto.NewAesGcm(c.responseBodyKey[:])
@ -363,7 +371,7 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read
} }
sizeParser = NewAEADSizeParser(lengthAuth) sizeParser = NewAEADSizeParser(lengthAuth)
} }
return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil
case protocol.SecurityType_CHACHA20_POLY1305: case protocol.SecurityType_CHACHA20_POLY1305:
aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.responseBodyKey[:])) aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.responseBodyKey[:]))
@ -384,9 +392,9 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read
} }
sizeParser = NewAEADSizeParser(lengthAuth) sizeParser = NewAEADSizeParser(lengthAuth)
} }
return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil
default: default:
panic("Unknown security type.") return nil, newError("invalid option: Security")
} }
} }

View File

@ -305,21 +305,25 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request
} }
// DecodeRequestBody returns Reader from which caller can fetch decrypted body. // DecodeRequestBody returns Reader from which caller can fetch decrypted body.
func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reader io.Reader) buf.Reader { func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reader io.Reader) (buf.Reader, error) {
var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{} var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{}
if request.Option.Has(protocol.RequestOptionChunkMasking) { if request.Option.Has(protocol.RequestOptionChunkMasking) {
sizeParser = NewShakeSizeParser(s.requestBodyIV[:]) sizeParser = NewShakeSizeParser(s.requestBodyIV[:])
} }
var padding crypto.PaddingLengthGenerator var padding crypto.PaddingLengthGenerator
if request.Option.Has(protocol.RequestOptionGlobalPadding) { if request.Option.Has(protocol.RequestOptionGlobalPadding) {
padding = sizeParser.(crypto.PaddingLengthGenerator) var ok bool
padding, ok = sizeParser.(crypto.PaddingLengthGenerator)
if !ok {
return nil, newError("invalid option: RequestOptionGlobalPadding")
}
} }
switch request.Security { switch request.Security {
case protocol.SecurityType_NONE: case protocol.SecurityType_NONE:
if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Option.Has(protocol.RequestOptionChunkStream) {
if request.Command.TransferType() == protocol.TransferTypeStream { if request.Command.TransferType() == protocol.TransferTypeStream {
return crypto.NewChunkStreamReader(sizeParser, reader) return crypto.NewChunkStreamReader(sizeParser, reader), nil
} }
auth := &crypto.AEADAuthenticator{ auth := &crypto.AEADAuthenticator{
@ -327,9 +331,9 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade
NonceGenerator: crypto.GenerateEmptyBytes(), NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
} }
return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding) return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding), nil
} }
return buf.NewReader(reader) return buf.NewReader(reader), nil
case protocol.SecurityType_LEGACY: case protocol.SecurityType_LEGACY:
aesStream := crypto.NewAesDecryptionStream(s.requestBodyKey[:], s.requestBodyIV[:]) aesStream := crypto.NewAesDecryptionStream(s.requestBodyKey[:], s.requestBodyIV[:])
@ -340,9 +344,9 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade
NonceGenerator: crypto.GenerateEmptyBytes(), NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
} }
return crypto.NewAuthenticationReader(auth, sizeParser, cryptionReader, request.Command.TransferType(), padding) return crypto.NewAuthenticationReader(auth, sizeParser, cryptionReader, request.Command.TransferType(), padding), nil
} }
return buf.NewReader(cryptionReader) return buf.NewReader(cryptionReader), nil
case protocol.SecurityType_AES128_GCM: case protocol.SecurityType_AES128_GCM:
aead := crypto.NewAesGcm(s.requestBodyKey[:]) aead := crypto.NewAesGcm(s.requestBodyKey[:])
@ -362,7 +366,7 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade
} }
sizeParser = NewAEADSizeParser(lengthAuth) sizeParser = NewAEADSizeParser(lengthAuth)
} }
return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil
case protocol.SecurityType_CHACHA20_POLY1305: case protocol.SecurityType_CHACHA20_POLY1305:
aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.requestBodyKey[:])) aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.requestBodyKey[:]))
@ -384,10 +388,10 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade
} }
sizeParser = NewAEADSizeParser(lengthAuth) sizeParser = NewAEADSizeParser(lengthAuth)
} }
return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil
default: default:
panic("Unknown security type.") return nil, newError("invalid option: Security")
} }
} }
@ -448,21 +452,25 @@ func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, wr
} }
// EncodeResponseBody returns a Writer that auto-encrypt content written by caller. // EncodeResponseBody returns a Writer that auto-encrypt content written by caller.
func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writer io.Writer) buf.Writer { func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) {
var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{} var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{}
if request.Option.Has(protocol.RequestOptionChunkMasking) { if request.Option.Has(protocol.RequestOptionChunkMasking) {
sizeParser = NewShakeSizeParser(s.responseBodyIV[:]) sizeParser = NewShakeSizeParser(s.responseBodyIV[:])
} }
var padding crypto.PaddingLengthGenerator var padding crypto.PaddingLengthGenerator
if request.Option.Has(protocol.RequestOptionGlobalPadding) { if request.Option.Has(protocol.RequestOptionGlobalPadding) {
padding = sizeParser.(crypto.PaddingLengthGenerator) var ok bool
padding, ok = sizeParser.(crypto.PaddingLengthGenerator)
if !ok {
return nil, newError("invalid option: RequestOptionGlobalPadding")
}
} }
switch request.Security { switch request.Security {
case protocol.SecurityType_NONE: case protocol.SecurityType_NONE:
if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Option.Has(protocol.RequestOptionChunkStream) {
if request.Command.TransferType() == protocol.TransferTypeStream { if request.Command.TransferType() == protocol.TransferTypeStream {
return crypto.NewChunkStreamWriter(sizeParser, writer) return crypto.NewChunkStreamWriter(sizeParser, writer), nil
} }
auth := &crypto.AEADAuthenticator{ auth := &crypto.AEADAuthenticator{
@ -470,9 +478,9 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ
NonceGenerator: crypto.GenerateEmptyBytes(), NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
} }
return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding) return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding), nil
} }
return buf.NewWriter(writer) return buf.NewWriter(writer), nil
case protocol.SecurityType_LEGACY: case protocol.SecurityType_LEGACY:
if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Option.Has(protocol.RequestOptionChunkStream) {
@ -481,9 +489,9 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ
NonceGenerator: crypto.GenerateEmptyBytes(), NonceGenerator: crypto.GenerateEmptyBytes(),
AdditionalDataGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
} }
return crypto.NewAuthenticationWriter(auth, sizeParser, s.responseWriter, request.Command.TransferType(), padding) return crypto.NewAuthenticationWriter(auth, sizeParser, s.responseWriter, request.Command.TransferType(), padding), nil
} }
return &buf.SequentialWriter{Writer: s.responseWriter} return &buf.SequentialWriter{Writer: s.responseWriter}, nil
case protocol.SecurityType_AES128_GCM: case protocol.SecurityType_AES128_GCM:
aead := crypto.NewAesGcm(s.responseBodyKey[:]) aead := crypto.NewAesGcm(s.responseBodyKey[:])
@ -503,7 +511,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ
} }
sizeParser = NewAEADSizeParser(lengthAuth) sizeParser = NewAEADSizeParser(lengthAuth)
} }
return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil
case protocol.SecurityType_CHACHA20_POLY1305: case protocol.SecurityType_CHACHA20_POLY1305:
aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.responseBodyKey[:])) aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.responseBodyKey[:]))
@ -525,9 +533,9 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ
} }
sizeParser = NewAEADSizeParser(lengthAuth) sizeParser = NewAEADSizeParser(lengthAuth)
} }
return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil
default: default:
panic("Unknown security type.") return nil, newError("invalid option: Security")
} }
} }

View File

@ -180,8 +180,10 @@ func (h *Handler) RemoveUser(ctx context.Context, email string) error {
func transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSession, request *protocol.RequestHeader, response *protocol.ResponseHeader, input buf.Reader, output *buf.BufferedWriter) error { func transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSession, request *protocol.RequestHeader, response *protocol.ResponseHeader, input buf.Reader, output *buf.BufferedWriter) error {
session.EncodeResponseHeader(response, output) session.EncodeResponseHeader(response, output)
bodyWriter := session.EncodeResponseBody(request, output) bodyWriter, err := session.EncodeResponseBody(request, output)
if err != nil {
return newError("failed to start decoding response").Base(err)
}
{ {
// Optimize for small response packet // Optimize for small response packet
data, err := input.ReadMultiBuffer() data, err := input.ReadMultiBuffer()
@ -288,7 +290,10 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection i
requestDone := func() error { requestDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
bodyReader := svrSession.DecodeRequestBody(request, reader) bodyReader, err := svrSession.DecodeRequestBody(request, reader)
if err != nil {
return newError("failed to start decoding").Base(err)
}
if err := buf.Copy(bodyReader, link.Writer, buf.UpdateActivity(timer)); err != nil { if err := buf.Copy(bodyReader, link.Writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transfer request").Base(err) return newError("failed to transfer request").Base(err)
} }

View File

@ -149,7 +149,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return newError("failed to encode request").Base(err).AtWarning() return newError("failed to encode request").Base(err).AtWarning()
} }
bodyWriter := session.EncodeRequestBody(request, writer) bodyWriter, err := session.EncodeRequestBody(request, writer)
if err != nil {
return newError("failed to start encoding").Base(err)
}
if err := buf.CopyOnceTimeout(input, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { if err := buf.CopyOnceTimeout(input, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {
return newError("failed to write first payload").Base(err) return newError("failed to write first payload").Base(err)
} }
@ -181,8 +184,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
h.handleCommand(rec.Destination(), header.Command) h.handleCommand(rec.Destination(), header.Command)
bodyReader := session.DecodeResponseBody(request, reader) bodyReader, err := session.DecodeResponseBody(request, reader)
if err != nil {
return newError("failed to start encoding response").Base(err)
}
return buf.Copy(bodyReader, output, buf.UpdateActivity(timer)) return buf.Copy(bodyReader, output, buf.UpdateActivity(timer))
} }