-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathconn_read.go
101 lines (90 loc) · 3.17 KB
/
conn_read.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package reader
import (
"context"
"errors"
"io"
"syscall"
"time"
"github.com/docker/go-units"
contextutil "github.com/projectdiscovery/utils/context"
errorutil "github.com/projectdiscovery/utils/errors"
)
var (
// although this is more than enough for most cases
MaxReadSize, _ = units.FromHumanSize("8mb")
)
var (
ErrTooLarge = errors.New("reader: too large only 8MB allowed as per MaxReadSize")
)
// ConnReadN reads at most N bytes from reader and it optimized
// for connection based readers like net.Conn it should not be used
// for file/buffer based reading, ConnReadN should be preferred
// instead of 'conn.Read() without loop' . It ignores EOF, UnexpectedEOF and timeout errors
// Note: you are responsible for adding a timeout to context
func ConnReadN(ctx context.Context, reader io.Reader, N int64) ([]byte, error) {
if N == -1 {
N = MaxReadSize
} else if N < -1 {
return nil, errors.New("reader: N cannot be less than -1")
} else if N == 0 {
return []byte{}, nil
} else if N > MaxReadSize {
return nil, ErrTooLarge
}
var readErr error
pr, pw := io.Pipe()
// When using the Nuclei network protocol to read all available data from a connection,
// there may be a timeout error after data has been sent by server. In this scenario,
// we should return the data and ignore the error (if it is a timeout error).
// To avoid race conditions, we use io.Pipe() along with a goroutine.
// For an example of this scenario, refer to TestConnReadN#6.
go func() {
defer pw.Close()
fn := func() (int64, error) {
return io.CopyN(pw, io.LimitReader(reader, N), N)
}
// ExecFuncWithTwoReturns will execute the function but errors if context is done
_, readErr = contextutil.ExecFuncWithTwoReturns(ctx, fn)
}()
// read from pipe and return
bin, err2 := io.ReadAll(pr)
if err2 != nil {
return nil, errorutil.NewWithErr(err2).Msgf("something went wrong while reading from pipe")
}
if readErr != nil {
if errorutil.IsTimeout(readErr) && len(bin) > 0 {
// if error is a timeout error and we have some data already
// then return data and ignore error
return bin, nil
} else if IsAcceptedError(readErr) {
// if error is accepted error ex: EOF, UnexpectedEOF, connection refused
// then return data and ignore error
return bin, nil
} else {
return nil, errorutil.WrapfWithNil(readErr, "reader: error while reading from connection")
}
} else {
return bin, nil
}
}
// ConnReadNWithTimeout is same as ConnReadN but it takes timeout
// instead of context and it returns error if read does not finish in given time
func ConnReadNWithTimeout(reader io.Reader, N int64, after time.Duration) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), after)
defer cancel()
return ConnReadN(ctx, reader, N)
}
// IsAcceptedError checks if the error is accepted error
// for example: connection refused, io.EOF, io.ErrUnexpectedEOF
// while reading from connection
func IsAcceptedError(err error) bool {
if err == io.EOF || err == io.ErrUnexpectedEOF {
// ideally we should error out if we get a timeout error but
// that's different for our use case
return true
}
if errors.Is(err, syscall.ECONNREFUSED) {
return true
}
return false
}