• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender / 8fbc121d225795f8eff22fd8b59bcf6c56fa43ac

pending completion
8fbc121d225795f8eff22fd8b59bcf6c56fa43ac

push

gitlab-ci

GitHub
Merge pull request #1178 from pasinskim/master

9809 of 12349 relevant lines covered (79.43%)

29.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.44
/client/test/http_proxy.go
1
// Copyright 2022 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
package test
16

17
import (
18
        "bufio"
19
        "encoding/base64"
20
        "fmt"
21
        "io"
22
        "net"
23
        "net/http"
24
        "net/url"
25
        "os"
26
        "strings"
27
        "sync"
28

29
        "github.com/mendersoftware/mender/client"
30

31
        "github.com/pkg/errors"
32
)
33

34
func init() {
2✔
35
        client.ProxyURLFromHostPortGetter = func(addr string) (*url.URL, error) {
4✔
36
                p := os.Getenv("HTTPS_PROXY")
2✔
37
                if p == "" {
4✔
38
                        return nil, nil
2✔
39
                }
2✔
40
                return url.Parse(p)
2✔
41
        }
42
}
43

44
const (
45
        inetAddrLoopback = "127.0.0.1:0"
46
        username         = "user"
47
        password         = "password"
48
)
49

50
type connection struct {
51
        clientConn net.Conn
52
        serverConn net.Conn
53
        wg         *sync.WaitGroup
54
}
55

56
type TestHttpProxy struct {
57
        isRunning          bool
58
        listener           net.Listener
59
        quit               chan interface{}
60
        wg                 *sync.WaitGroup
61
        errors             []error
62
        connections        map[*connection]bool
63
        mut                *sync.Mutex
64
        requireAuth        bool
65
        numRequiredConns   int
66
        numSuccessfulConns int
67
}
68

69
func NewTestHttpProxy(
70
        numRequiredConns int,
71
        requireAuth bool,
72
) (*TestHttpProxy, error) {
2✔
73
        listener, err := net.Listen("tcp", inetAddrLoopback)
2✔
74
        if err != nil {
2✔
75
                return nil, err
×
76
        }
×
77
        p := &TestHttpProxy{
2✔
78
                isRunning:        true,
2✔
79
                listener:         listener,
2✔
80
                quit:             make(chan interface{}),
2✔
81
                wg:               new(sync.WaitGroup),
2✔
82
                errors:           make([]error, 0),
2✔
83
                connections:      make(map[*connection]bool),
2✔
84
                mut:              &sync.Mutex{},
2✔
85
                requireAuth:      requireAuth,
2✔
86
                numRequiredConns: numRequiredConns,
2✔
87
        }
2✔
88
        p.wg.Add(1)
2✔
89
        go p.serve()
2✔
90
        return p, nil
2✔
91
}
92

93
func (p *TestHttpProxy) GetUrl() string {
2✔
94
        if !p.requireAuth {
4✔
95
                return "http://" + p.listener.Addr().String()
2✔
96
        }
2✔
97
        return "http://" + username + ":" + password + "@" + p.listener.Addr().String()
2✔
98
}
99

100
func (p *TestHttpProxy) Stop() error {
2✔
101
        if p == nil {
2✔
102
                return nil
×
103
        }
×
104
        close(p.quit)
2✔
105
        p.listener.Close()
2✔
106
        p.mut.Lock()
2✔
107
        p.isRunning = false
2✔
108
        for c := range p.connections {
4✔
109
                c.clientConn.Close()
2✔
110
                c.serverConn.Close()
2✔
111
        }
2✔
112
        p.mut.Unlock()
2✔
113
        p.wg.Wait()
2✔
114

2✔
115
        if len(p.errors) > 0 {
2✔
116
                return p.errors[0]
×
117
        }
×
118

119
        if p.numRequiredConns >= 0 && p.numRequiredConns != p.numSuccessfulConns {
2✔
120
                return errors.Errorf(
×
121
                        "number of successful connections %d "+
×
122
                                "was not equal to required amount of %d",
×
123
                        p.numSuccessfulConns,
×
124
                        p.numRequiredConns)
×
125
        }
×
126

127
        return nil
2✔
128
}
129

130
func (p *TestHttpProxy) serve() {
2✔
131
        defer p.wg.Done()
2✔
132

2✔
133
        for {
4✔
134
                conn, err := p.listener.Accept()
2✔
135
                if err != nil {
4✔
136
                        select {
2✔
137
                        case <-p.quit:
2✔
138
                                return
2✔
139
                        default:
×
140
                                p.appendError(errors.Wrap(err, "failed to accept connection"))
×
141
                                return
×
142
                        }
143
                } else {
2✔
144
                        p.wg.Add(1)
2✔
145
                        go func() {
4✔
146
                                defer p.wg.Done()
2✔
147
                                p.handleConnection(conn)
2✔
148
                        }()
2✔
149
                }
150
        }
151
}
152

153
func (p *TestHttpProxy) handleConnection(conn net.Conn) {
2✔
154
        defer conn.Close()
2✔
155
        br := bufio.NewReader(conn)
2✔
156
        req, err := http.ReadRequest(br)
2✔
157
        if err != nil {
2✔
158
                p.appendError(errors.Wrap(err, "failed to read http request"))
×
159
                return
×
160
        }
×
161

162
        if req.Method != http.MethodConnect {
2✔
163
                if err := sendResponse(conn, http.StatusBadRequest); err != nil {
×
164
                        p.appendError(err)
×
165
                }
×
166
                p.appendError(errors.New("initial http request method was not CONNECT"))
×
167
                return
×
168
        }
169

170
        if err = p.handleAuth(req); err != nil {
2✔
171
                if sendErr := sendResponse(conn,
×
172
                        http.StatusProxyAuthRequired); sendErr != nil {
×
173
                        p.appendError(sendErr)
×
174
                }
×
175
                p.appendError(err)
×
176
                return
×
177
        }
178

179
        forwardConn, err := net.Dial("tcp", req.Host)
2✔
180
        if err != nil {
2✔
181
                p.appendError(errors.New("failed to dial forwarding target"))
×
182
                return
×
183
        }
×
184
        defer forwardConn.Close()
2✔
185

2✔
186
        if err := sendResponse(conn, http.StatusOK); err != nil {
2✔
187
                p.appendError(err)
×
188
                return
×
189
        }
×
190

191
        c := &connection{
2✔
192
                clientConn: conn,
2✔
193
                serverConn: forwardConn,
2✔
194
                wg:         new(sync.WaitGroup),
2✔
195
        }
2✔
196

2✔
197
        p.mut.Lock()
2✔
198
        p.numSuccessfulConns++
2✔
199
        if !p.isRunning {
2✔
200
                p.mut.Unlock()
×
201
                return
×
202
        }
×
203
        p.connections[c] = true
2✔
204
        p.mut.Unlock()
2✔
205

2✔
206
        c.wg.Add(2)
2✔
207
        go forwardConnection(c.wg, c.clientConn, c.serverConn)
2✔
208
        go forwardConnection(c.wg, c.serverConn, c.clientConn)
2✔
209
        c.wg.Wait()
2✔
210

2✔
211
        p.mut.Lock()
2✔
212
        delete(p.connections, c)
2✔
213
        p.mut.Unlock()
2✔
214
}
215
func (p *TestHttpProxy) handleAuth(req *http.Request) error {
2✔
216
        if !p.requireAuth {
4✔
217
                return nil
2✔
218
        }
2✔
219
        if h := req.Header["Proxy-Authorization"]; h != nil {
4✔
220
                h = strings.Split(h[0], " ")
2✔
221
                if len(h) != 2 {
2✔
222
                        return errors.New("received auth header with bad format")
×
223
                }
×
224
                if t := h[0]; t != "Basic" {
2✔
225
                        return errors.Errorf("received auth header with non-Basic type '%s'", t)
×
226
                }
×
227
                bytes, err := base64.StdEncoding.DecodeString(h[1])
2✔
228
                if err != nil {
2✔
229
                        return errors.New("failed to base64 decode credentials")
×
230
                }
×
231
                creds := strings.Split(string(bytes), ":")
2✔
232
                if len(creds) != 2 {
2✔
233
                        return errors.New("received credentials in bad format")
×
234
                }
×
235
                if creds[0] != username || creds[1] != password {
2✔
236
                        return errors.New("username or password did not match")
×
237
                }
×
238
        } else {
×
239
                return errors.New("required auth header absent from connect request")
×
240
        }
×
241
        return nil
2✔
242
}
243

244
func (p *TestHttpProxy) appendError(err error) {
×
245
        p.mut.Lock()
×
246
        p.errors = append(p.errors, err)
×
247
        p.mut.Unlock()
×
248
}
×
249

250
func sendResponse(conn net.Conn, statusCode int) error {
2✔
251
        if err := (&http.Response{
2✔
252
                ProtoMajor: 1,
2✔
253
                ProtoMinor: 1,
2✔
254
                StatusCode: statusCode,
2✔
255
        }).Write(conn); err != nil {
2✔
256
                return errors.Wrap(err,
×
257
                        fmt.Sprintf("failed to write '%d: %s; back to client",
×
258
                                statusCode,
×
259
                                http.StatusText(statusCode)))
×
260
        }
×
261
        return nil
2✔
262
}
263

264
func forwardConnection(wg *sync.WaitGroup, src net.Conn, dst net.Conn) {
2✔
265
        _, _ = io.Copy(dst, src)
2✔
266
        src.Close()
2✔
267
        dst.Close()
2✔
268
        wg.Done()
2✔
269
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc