-
Notifications
You must be signed in to change notification settings - Fork 26
/
connection.go
129 lines (100 loc) · 2.83 KB
/
connection.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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Copyright 2023 OWASP ModSecurity Core Rule Set Project
// SPDX-License-Identifier: Apache-2.0
// Package ftwhttp provides low level abstractions for sending/receiving raw http messages
package ftwhttp
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strconv"
"time"
"github.com/rs/zerolog/log"
)
// DestinationFromString create a Destination from String
func DestinationFromString(urlString string) (*Destination, error) {
u, err := url.Parse(urlString)
if err != nil {
return nil, err
}
host, port, _ := net.SplitHostPort(u.Host)
p, _ := strconv.Atoi(port)
d := &Destination{
Port: p,
DestAddr: host,
Protocol: u.Scheme,
}
return d, nil
}
// StartTrackingTime initializes timer
func (c *Connection) StartTrackingTime() {
c.duration.StartTracking()
}
// StopTrackingTime stops timer
func (c *Connection) StopTrackingTime() {
c.duration.StopTracking()
}
// GetTrackedTime will return the time since the request started and the response was parsed
func (c *Connection) GetTrackedTime() *RoundTripTime {
return c.duration
}
func (c *Connection) send(data []byte) (int, error) {
var err error
var sent int
log.Trace().Msg("ftw/http: sending data")
// Store times for searching in logs, if necessary
if c.connection != nil {
sent, err = c.connection.Write(data)
} else {
err = errors.New("ftw/http/send: not connected to server")
}
return sent, err
}
func (c *Connection) receive() (io.Reader, error) {
log.Trace().Msg("ftw/http: receiving data")
// We assume the response body can be handled in memory without problems
// That's why we use io.ReadAll
if err := c.connection.SetReadDeadline(time.Now().Add(c.readTimeout)); err != nil {
return nil, err
}
return c.connection, nil
}
// Request will use all the inputs and send a raw http request to the destination
func (c *Connection) Request(request *Request) error {
// Build request first, then connect and send, so timers are accurate
data, err := buildRequest(request)
if err != nil {
return fmt.Errorf("ftw/http: fatal error building request: %w", err)
}
log.Debug().Msgf("ftw/http: sending data:\n%s\n", data)
_, err = c.send(data)
if err != nil {
log.Error().Msgf("ftw/http: error writing data: %s", err.Error())
}
return err
}
// Response reads the response sent by the WAF and return the corresponding struct
// It leverages the go stdlib for reading and parsing the response
func (c *Connection) Response() (*Response, error) {
r, err := c.receive()
if err != nil {
return nil, err
}
buf := &bytes.Buffer{}
reader := *bufio.NewReader(io.TeeReader(r, buf))
httpResponse, err := http.ReadResponse(&reader, nil)
if err != nil {
return nil, err
}
data := buf.Bytes()
log.Debug().Msgf("ftw/http: received data - %q", data)
response := Response{
RAW: data,
Parsed: *httpResponse,
}
return &response, err
}