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

mendersoftware / mender / 1019272738

28 Sep 2023 07:18AM UTC coverage: 77.556% (-0.007%) from 77.563%
1019272738

push

gitlab-ci

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

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

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

6697 of 8635 relevant lines covered (77.56%)

10656.59 hits per line

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

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

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

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

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

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

278
        auto &cancelled = cancelled_;
4✔
279

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

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

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

300
                                local_socket->Cancel();
6✔
301
                                remote_socket->Cancel();
6✔
302

303
                                if (!*cancelled) {
6✔
304
                                        connections_.erase(req_in);
6✔
305
                                }
306
                        };
10✔
307

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

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

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

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

337
} // namespace http_forwarder
338
} // namespace auth
339
} // 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