// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package saferio provides I/O functions that avoid allocating large // amounts of memory unnecessarily. This is intended for packages that // read data from an [io.Reader] where the size is part of the input // data but the input may be corrupt, or may be provided by an // untrustworthy attacker.
package saferio import ( ) // chunk is an arbitrary limit on how much memory we are willing // to allocate without concern. const chunk = 10 << 20 // 10M // ReadData reads n bytes from the input stream, but avoids allocating // all n bytes if n is large. This avoids crashing the program by // allocating all n bytes in cases where n is incorrect. // // The error is io.EOF only if no bytes were read. // If an io.EOF happens after reading some but not all the bytes, // ReadData returns io.ErrUnexpectedEOF. func ( io.Reader, uint64) ([]byte, error) { if int64() < 0 || != uint64(int()) { // n is too large to fit in int, so we can't allocate // a buffer large enough. Treat this as a read failure. return nil, io.ErrUnexpectedEOF } if < chunk { := make([]byte, ) , := io.ReadFull(, ) if != nil { return nil, } return , nil } var []byte := make([]byte, chunk) for > 0 { := if > chunk { = chunk } , := io.ReadFull(, [:]) if != nil { if len() > 0 && == io.EOF { = io.ErrUnexpectedEOF } return nil, } = append(, [:]...) -= } return , nil } // ReadDataAt reads n bytes from the input stream at off, but avoids // allocating all n bytes if n is large. This avoids crashing the program // by allocating all n bytes in cases where n is incorrect. func ( io.ReaderAt, uint64, int64) ([]byte, error) { if int64() < 0 || != uint64(int()) { // n is too large to fit in int, so we can't allocate // a buffer large enough. Treat this as a read failure. return nil, io.ErrUnexpectedEOF } if < chunk { := make([]byte, ) , := .ReadAt(, ) if != nil { // io.SectionReader can return EOF for n == 0, // but for our purposes that is a success. if != io.EOF || > 0 { return nil, } } return , nil } var []byte := make([]byte, chunk) for > 0 { := if > chunk { = chunk } , := .ReadAt([:], ) if != nil { return nil, } = append(, [:]...) -= += int64() } return , nil } // SliceCapWithSize returns the capacity to use when allocating a slice. // After the slice is allocated with the capacity, it should be // built using append. This will avoid allocating too much memory // if the capacity is large and incorrect. // // A negative result means that the value is always too big. func (, uint64) int { if int64() < 0 || != uint64(int()) { return -1 } if > 0 && > (1<<64-1)/ { return -1 } if * > chunk { = chunk / if == 0 { = 1 } } return int() } // SliceCap is like SliceCapWithSize but using generics. func [ any]( uint64) int { var := uint64(unsafe.Sizeof()) return SliceCapWithSize(, ) }