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

mendersoftware / mender / 1019238693

28 Sep 2023 06:41AM UTC coverage: 77.563% (-0.5%) from 78.091%
1019238693

push

gitlab-ci

kacf
chore: Send 501 to client when server tries unsupported protocol switch.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

13 of 13 new or added lines in 1 file covered. (100.0%)

6696 of 8633 relevant lines covered (77.56%)

10658.93 hits per line

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

78.92
/mender-auth/http_forwarder/http_forwarder.cpp
1
// Copyright 2023 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
#include <mender-auth/http_forwarder.hpp>
16

17
namespace mender {
18
namespace auth {
19
namespace http_forwarder {
20

21
ForwardObject::ForwardObject(const http::ClientConfig &config, events::EventLoop &event_loop) :
12✔
22
        client_(config, event_loop),
23
        logger_("http_forwarder") {
12✔
24
}
12✔
25

26
Server::Server(
11✔
27
        const http::ServerConfig &server_config,
28
        const http::ClientConfig &client_config,
29
        events::EventLoop &loop) :
1✔
30
        logger_("http_forwarder"),
31
        event_loop_ {loop},
32
        server_ {server_config, loop},
33
        cancelled_ {make_shared<bool>(true)},
×
34
        client_config_ {client_config} {
11✔
35
}
11✔
36

37
Server::~Server() {
11✔
38
        *cancelled_ = true;
11✔
39
        cancelled_ = make_shared<bool>(true);
11✔
40
        Cancel();
11✔
41
}
11✔
42

43
void Server::Cancel() {
13✔
44
        *cancelled_ = true;
13✔
45
        cancelled_ = make_shared<bool>(true);
13✔
46
        connections_.clear();
13✔
47
        server_.Cancel();
13✔
48
}
13✔
49

50
error::Error Server::AsyncForward(const string &listen_url, const string &target_url) {
14✔
51
        if (!*cancelled_) {
14✔
52
                return error::Error(
53
                        make_error_condition(errc::operation_in_progress),
1✔
54
                        "HTTP forwarding already in progress");
3✔
55
        }
56

57
        *cancelled_ = false;
13✔
58

59
        http::BrokenDownUrl target_address;
26✔
60

61
        // We don't actually need target_address here, but break it down here anyway to avoid that
62
        // the error shows up much later when the first connection is made.
63
        auto err = http::BreakDownUrl(target_url, target_address);
26✔
64
        if (err != error::NoError) {
13✔
65
                return err.WithContext("HTTP forwarder: Invalid target address");
×
66
        }
67
        target_url_ = target_url;
13✔
68

69
        auto &cancelled = cancelled_;
13✔
70
        err = server_.AsyncServeUrl(
13✔
71
                listen_url,
72
                [this, cancelled](http::ExpectedIncomingRequestPtr exp_req) {
24✔
73
                        if (!*cancelled) {
12✔
74
                                RequestHeaderHandler(exp_req);
12✔
75
                        }
76
                },
12✔
77
                [this, cancelled](http::IncomingRequestPtr req, error::Error err) {
24✔
78
                        if (!*cancelled) {
12✔
79
                                RequestBodyHandler(req, err);
12✔
80
                        }
81
                });
38✔
82
        if (err != error::NoError) {
13✔
83
                return err.WithContext("Unable to start HTTP forwarding server");
×
84
        }
85

86
        return error::NoError;
13✔
87
}
88

89
uint16_t Server::GetPort() const {
1✔
90
        return server_.GetPort();
1✔
91
}
92

93
string Server::GetUrl() const {
12✔
94
        return server_.GetUrl();
12✔
95
}
96

97
void Server::RequestHeaderHandler(http::ExpectedIncomingRequestPtr exp_req) {
12✔
98
        if (!exp_req) {
12✔
99
                logger_.Error("Error in incoming request: " + exp_req.error().String());
×
100
                return;
×
101
        }
102
        auto &req_in = exp_req.value();
12✔
103

104
        ForwardObjectPtr connection {new ForwardObject(client_config_, event_loop_)};
12✔
105
        connections_[req_in] = connection;
12✔
106
        connection->logger_ = logger_.WithFields(log::LogField {"request", req_in->GetPath()});
12✔
107
        connection->req_in_ = req_in;
12✔
108

109
        auto final_url = http::JoinUrl(target_url_, req_in->GetPath());
12✔
110
        auto req_out = make_shared<http::OutgoingRequest>();
12✔
111
        req_out->SetMethod(req_in->GetMethod());
12✔
112
        req_out->SetAddress(final_url);
12✔
113
        for (auto header : req_in->GetHeaders()) {
29✔
114
                req_out->SetHeader(header.first, header.second);
17✔
115
        }
116
        connection->req_out_ = req_out;
12✔
117

118
        auto exp_body_reader = req_in->MakeBodyAsyncReader();
12✔
119
        if (exp_body_reader) {
12✔
120
                auto body_reader = exp_body_reader.value();
10✔
121
                auto generated = make_shared<bool>(false);
5✔
122
                req_out->SetAsyncBodyGenerator([body_reader, generated]() -> io::ExpectedAsyncReaderPtr {
10✔
123
                        // We can only do this once, because the incoming request body is not
124
                        // seekable.
125
                        if (*generated) {
5✔
126
                                return expected::unexpected(error::Error(
×
127
                                        make_error_condition(errc::invalid_seek),
×
128
                                        "Cannot rewind HTTP stream to regenerate body"));
×
129
                        } else {
130
                                *generated = true;
5✔
131
                                return body_reader;
5✔
132
                        }
133
                });
10✔
134
        } else if (exp_body_reader.error().code != http::MakeError(http::BodyMissingError, "").code) {
7✔
135
                connection->logger_.Error(
×
136
                        "Could not get body reader for request: " + exp_body_reader.error().String());
×
137
                connections_.erase(req_in);
×
138
                return;
×
139
        } // else: if body is missing we don't need to do anything.
140

141
        auto &cancelled = cancelled_;
12✔
142
        auto err = connection->client_.AsyncCall(
12✔
143
                req_out,
144
                [this, cancelled, req_in](http::ExpectedIncomingResponsePtr exp_resp) {
24✔
145
                        if (!*cancelled) {
12✔
146
                                ResponseHeaderHandler(req_in, exp_resp);
12✔
147
                        }
148
                },
12✔
149
                [this, cancelled, req_in](http::ExpectedIncomingResponsePtr exp_resp) {
6✔
150
                        if (!*cancelled) {
3✔
151
                                ResponseBodyHandler(req_in, exp_resp);
3✔
152
                        }
153
                });
27✔
154
}
155

156
void Server::RequestBodyHandler(http::IncomingRequestPtr req_in, error::Error err) {
12✔
157
        auto maybe_connection = connections_.find(req_in);
12✔
158
        if (maybe_connection == connections_.end()) {
12✔
159
                // Can happen if the request was cancelled.
160
                return;
2✔
161
        }
162
        auto &connection = maybe_connection->second;
10✔
163

164
        if (err != error::NoError) {
10✔
165
                connection->logger_.Error("Error while reading incoming request body: " + err.String());
×
166
                connections_.erase(req_in);
×
167
                return;
×
168
        }
169

170
        auto exp_resp_out = connection->req_in_->MakeResponse();
10✔
171
        if (!exp_resp_out) {
10✔
172
                connection->logger_.Error(
×
173
                        "Could not make outgoing response: " + exp_resp_out.error().String());
×
174
                connections_.erase(req_in);
×
175
                return;
×
176
        }
177
        connection->resp_out_ = exp_resp_out.value();
10✔
178
}
179

180
void Server::ResponseHeaderHandler(
12✔
181
        http::IncomingRequestPtr req_in, http::ExpectedIncomingResponsePtr exp_resp_in) {
182
        auto connection = connections_[req_in];
12✔
183

184
        if (!exp_resp_in) {
12✔
185
                connection->logger_.Error("Error in incoming response: " + exp_resp_in.error().String());
3✔
186
                connections_.erase(req_in);
3✔
187
                return;
3✔
188
        }
189
        connection->resp_in_ = exp_resp_in.value();
9✔
190
        auto &resp_in = connection->resp_in_;
9✔
191

192
        auto &resp_out = connection->resp_out_;
9✔
193

194
        resp_out->SetStatusCodeAndMessage(resp_in->GetStatusCode(), resp_in->GetStatusMessage());
9✔
195
        for (auto header : resp_in->GetHeaders()) {
13✔
196
                resp_out->SetHeader(header.first, header.second);
4✔
197
        }
198

199
        auto &cancelled = cancelled_;
9✔
200

201
        auto exp_body_reader = resp_in->MakeBodyAsyncReader();
9✔
202

203
        if (resp_in->GetStatusCode() == http::StatusSwitchingProtocols) {
9✔
204
                if (exp_body_reader) {
5✔
205
                        string msg =
206
                                "Response both requested to switch protocol, and has a body, which is not supported";
2✔
207
                        connection->logger_.Error(msg);
1✔
208
                        exp_body_reader.value()->Cancel();
1✔
209
                        resp_out->SetStatusCodeAndMessage(http::StatusNotImplemented, msg);
1✔
210
                        // Remove body if we set it previously.
211
                        resp_out->SetHeader("Content-Length", "0");
1✔
212
                        auto err = resp_out->AsyncReply([cancelled, this, req_in](error::Error err) {
2✔
213
                                if (*cancelled) {
1✔
214
                                        return;
×
215
                                }
216

217
                                if (err != error::NoError) {
1✔
218
                                        connections_[req_in]->logger_.Error(
×
219
                                                "Error while replying to client: " + err.String());
×
220
                                }
221
                                connections_.erase(req_in);
1✔
222
                        });
2✔
223
                } else {
224
                        SwitchProtocol(req_in, resp_in, resp_out);
4✔
225
                }
226
                return;
5✔
227
        } else if (exp_body_reader) {
4✔
228
                resp_out->SetAsyncBodyReader(exp_body_reader.value());
3✔
229
        } else if (exp_body_reader.error().code != http::MakeError(http::BodyMissingError, "").code) {
1✔
230
                connection->logger_.Error(
×
231
                        "Could not get body reader for response: " + exp_body_reader.error().String());
×
232
                connections_.erase(req_in);
×
233
                return;
×
234
        } // else: if body is missing we don't need to do anything.
235

236
        auto err = resp_out->AsyncReply([cancelled, this, req_in](error::Error err) {
12✔
237
                if (*cancelled) {
4✔
238
                        return;
×
239
                }
240

241
                if (err != error::NoError) {
4✔
242
                        connections_[req_in]->logger_.Error(
4✔
243
                                "Error while forwarding response to client: " + err.String());
4✔
244
                        connections_.erase(req_in);
2✔
245
                        return;
2✔
246
                }
247

248
                auto &connection = connections_[req_in];
2✔
249
                connection->incoming_request_finished_ = true;
2✔
250
                if (connection->outgoing_request_finished_) {
2✔
251
                        // We are done, remove connection.
252
                        connections_.erase(req_in);
2✔
253
                }
254
        });
8✔
255
        if (err != error::NoError) {
4✔
256
                connection->logger_.Error("Could not forward response to client: " + err.String());
×
257
                connections_.erase(req_in);
×
258
                return;
×
259
        }
260
}
261

262
void Server::SwitchProtocol(
4✔
263
        http::IncomingRequestPtr req_in,
264
        http::IncomingResponsePtr resp_in,
265
        http::OutgoingResponsePtr resp_out) {
266
        auto exp_remote_socket = resp_in->SwitchProtocol();
4✔
267
        if (!exp_remote_socket) {
4✔
268
                connections_[req_in]->logger_.Error(
×
269
                        "Could not switch protocol: " + exp_remote_socket.error().String());
×
270
                connections_.erase(req_in);
×
271
                return;
×
272
        }
273
        auto &remote_socket = exp_remote_socket.value();
4✔
274

275
        auto &cancelled = cancelled_;
4✔
276

277
        auto err = resp_out->AsyncSwitchProtocol([cancelled, this, req_in, resp_out, remote_socket](
4✔
278
                                                                                                 io::ExpectedAsyncReadWriterPtr exp_local_socket) {
4✔
279
                if (*cancelled) {
4✔
280
                        return;
×
281
                }
282

283
                if (!exp_local_socket) {
4✔
284
                        connections_[req_in]->logger_.Error(
×
285
                                "Could not switch protocol: " + exp_local_socket.error().String());
×
286
                        connections_.erase(req_in);
×
287
                        return;
×
288
                }
289
                auto &local_socket = exp_local_socket.value();
4✔
290

291
                auto finished_handler =
292
                        [this, req_in, cancelled, local_socket, remote_socket](error::Error err) {
24✔
293
                                if (!*cancelled && err != error::NoError) {
6✔
294
                                        log::Error("Error during network socket forwarding: " + err.String());
6✔
295
                                }
296

297
                                local_socket->Cancel();
6✔
298
                                remote_socket->Cancel();
6✔
299

300
                                if (!*cancelled) {
6✔
301
                                        connections_.erase(req_in);
6✔
302
                                }
303
                        };
10✔
304

305
                // Forward in both directions.
306
                io::AsyncCopy(local_socket, remote_socket, finished_handler);
4✔
307
                io::AsyncCopy(remote_socket, local_socket, finished_handler);
4✔
308
        });
8✔
309
        if (err != error::NoError) {
4✔
310
                connections_[req_in]->logger_.Error("Could not switch protocol: " + err.String());
×
311
                connections_.erase(req_in);
×
312
                return;
×
313
        }
314
}
315

316
void Server::ResponseBodyHandler(
3✔
317
        http::IncomingRequestPtr req_in, http::ExpectedIncomingResponsePtr exp_resp_in) {
318
        auto &connection = connections_[req_in];
3✔
319

320
        if (!exp_resp_in) {
3✔
321
                connection->logger_.Error(
2✔
322
                        "Error while reading incoming response body: " + exp_resp_in.error().String());
2✔
323
                connections_.erase(req_in);
1✔
324
                return;
1✔
325
        }
326

327
        connection->outgoing_request_finished_ = true;
2✔
328
        if (connection->incoming_request_finished_) {
2✔
329
                // We are done, remove connection.
330
                connections_.erase(req_in);
×
331
        }
332
}
333

334
} // namespace http_forwarder
335
} // namespace auth
336
} // namespace mender
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