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

mendersoftware / mender / 1039476627

17 Oct 2023 10:49AM UTC coverage: 79.701% (-0.6%) from 80.278%
1039476627

push

gitlab-ci

oleorhagen
fix(mender-auth): Remember to pass in the needed params

Just add the missing identity script, and private key params.

Ticket: MEN-6671
Changelog: None

Signed-off-by: Ole Petter <ole.orhagen@northern.tech>

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

6557 of 8227 relevant lines covered (79.7%)

9764.5 hits per line

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

83.49
/common/http/http.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 <common/http.hpp>
16

17
#include <algorithm>
18
#include <cctype>
19
#include <cstdlib>
20
#include <iomanip>
21
#include <string>
22

23
#include <common/common.hpp>
24

25
namespace mender {
26
namespace http {
27

28
namespace common = mender::common;
29

30
const HttpErrorCategoryClass HttpErrorCategory;
31

32
const char *HttpErrorCategoryClass::name() const noexcept {
×
33
        return "HttpErrorCategory";
×
34
}
35

36
string HttpErrorCategoryClass::message(int code) const {
35✔
37
        switch (code) {
35✔
38
        case NoError:
39
                return "Success";
×
40
        case NoSuchHeaderError:
41
                return "No such header";
24✔
42
        case InvalidUrlError:
43
                return "Malformed URL";
×
44
        case BodyMissingError:
45
                return "Body is missing";
×
46
        case BodyIgnoredError:
47
                return "HTTP stream contains a body, but a reader has not been created for it";
×
48
        case UnsupportedMethodError:
49
                return "Unsupported HTTP method";
×
50
        case StreamCancelledError:
51
                return "Stream has been cancelled/destroyed";
×
52
        case UnsupportedBodyType:
53
                return "HTTP stream has a body type we don't understand";
×
54
        case MaxRetryError:
55
                return "Tried maximum number of times";
2✔
56
        case DownloadResumerError:
57
                return "Resume download error";
9✔
58
        }
59
        // Don't use "default" case. This should generate a warning if we ever add any enums. But
60
        // still assert here for safety.
61
        assert(false);
62
        return "Unknown";
×
63
}
64

65
error::Error MakeError(ErrorCode code, const string &msg) {
79✔
66
        return error::Error(error_condition(code, HttpErrorCategory), msg);
339✔
67
}
68

69
string MethodToString(Method method) {
168✔
70
        switch (method) {
168✔
71
        case Method::Invalid:
72
                return "Invalid";
×
73
        case Method::GET:
74
                return "GET";
153✔
75
        case Method::HEAD:
76
                return "HEAD";
×
77
        case Method::POST:
78
                return "POST";
×
79
        case Method::PUT:
80
                return "PUT";
15✔
81
        case Method::PATCH:
82
                return "PATCH";
×
83
        case Method::CONNECT:
84
                return "CONNECT";
×
85
        }
86
        // Don't use "default" case. This should generate a warning if we ever add any methods. But
87
        // still assert here for safety.
88
        assert(false);
89
        return "INVALID_METHOD";
×
90
}
91

92
error::Error BreakDownUrl(const string &url, BrokenDownUrl &address) {
384✔
93
        const string url_split {"://"};
384✔
94

95
        auto split_index = url.find(url_split);
384✔
96
        if (split_index == string::npos) {
384✔
97
                return MakeError(InvalidUrlError, url + " is not a valid URL.");
×
98
        }
99
        if (split_index == 0) {
384✔
100
                return MakeError(InvalidUrlError, url + ": missing hostname");
×
101
        }
102

103
        address.protocol = url.substr(0, split_index);
768✔
104

105
        auto tmp = url.substr(split_index + url_split.size());
384✔
106
        split_index = tmp.find("/");
384✔
107
        if (split_index == string::npos) {
384✔
108
                address.host = tmp;
243✔
109
                address.path = "/";
243✔
110
        } else {
111
                address.host = tmp.substr(0, split_index);
141✔
112
                address.path = tmp.substr(split_index);
282✔
113
        }
114

115
        split_index = address.host.find(":");
384✔
116
        if (split_index != string::npos) {
384✔
117
                tmp = std::move(address.host);
381✔
118
                address.host = tmp.substr(0, split_index);
381✔
119

120
                tmp = tmp.substr(split_index + 1);
381✔
121
                auto port = common::StringToLongLong(tmp);
381✔
122
                if (!port) {
381✔
123
                        return error::Error(port.error().code, url + " contains invalid port number");
×
124
                }
125
                address.port = port.value();
381✔
126
        } else {
127
                if (address.protocol == "http") {
3✔
128
                        address.port = 80;
1✔
129
                } else if (address.protocol == "https") {
2✔
130
                        address.port = 443;
1✔
131
                } else {
132
                        return error::Error(
133
                                make_error_condition(errc::protocol_not_supported),
2✔
134
                                "Cannot deduce port number from protocol " + address.protocol);
2✔
135
                }
136
        }
137

138
        log::Trace(
383✔
139
                "URL broken down into (protocol: " + address.protocol + "), (host: " + address.host
766✔
140
                + "), (port: " + to_string(address.port) + "), (path: " + address.path + ")");
1,149✔
141

142
        return error::NoError;
383✔
143
}
144

145
string URLEncode(const string &value) {
15✔
146
        stringstream escaped;
30✔
147
        escaped << hex;
15✔
148

149
        for (auto c : value) {
288✔
150
                // Keep alphanumeric and other accepted characters intact
151
                if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
273✔
152
                        escaped << c;
251✔
153
                } else {
154
                        // Any other characters are percent-encoded
155
                        escaped << uppercase;
22✔
156
                        escaped << '%' << setw(2) << int((unsigned char) c);
22✔
157
                        escaped << nouppercase;
22✔
158
                }
159
        }
160

161
        return escaped.str();
15✔
162
}
163

164
string JoinOneUrl(const string &prefix, const string &suffix) {
157✔
165
        auto prefix_end = prefix.cend();
166
        while (prefix_end != prefix.cbegin() && prefix_end[-1] == '/') {
169✔
167
                prefix_end--;
168
        }
169

170
        auto suffix_start = suffix.cbegin();
171
        while (suffix_start != suffix.cend() && *suffix_start == '/') {
217✔
172
                suffix_start++;
173
        }
174

175
        return string(prefix.cbegin(), prefix_end) + "/" + string(suffix_start, suffix.cend());
314✔
176
}
177

178
size_t CaseInsensitiveHasher::operator()(const string &str) const {
2,318✔
179
        string lower_str(str.length(), ' ');
180
        transform(
181
                str.begin(), str.end(), lower_str.begin(), [](unsigned char c) { return std::tolower(c); });
28,023✔
182
        return hash<string>()(lower_str);
2,318✔
183
}
184

185
bool CaseInsensitiveComparator::operator()(const string &str1, const string &str2) const {
985✔
186
        return strcasecmp(str1.c_str(), str2.c_str()) == 0;
985✔
187
}
188

189
expected::ExpectedString Transaction::GetHeader(const string &name) const {
688✔
190
        if (headers_.find(name) == headers_.end()) {
688✔
191
                return expected::unexpected(MakeError(NoSuchHeaderError, "No such header: " + name));
687✔
192
        }
193
        return headers_.at(name);
459✔
194
}
195

196
string Request::GetHost() const {
×
197
        return address_.host;
×
198
}
199

200
string Request::GetProtocol() const {
×
201
        return address_.protocol;
×
202
}
203

204
int Request::GetPort() const {
×
205
        return address_.port;
×
206
}
207

208
Method Request::GetMethod() const {
87✔
209
        return method_;
87✔
210
}
211

212
string Request::GetPath() const {
138✔
213
        return address_.path;
138✔
214
}
215

216
unsigned Response::GetStatusCode() const {
503✔
217
        return status_code_;
503✔
218
}
219

220
string Response::GetStatusMessage() const {
306✔
221
        return status_message_;
306✔
222
}
223

224
void BaseOutgoingRequest::SetMethod(Method method) {
179✔
225
        method_ = method;
179✔
226
}
179✔
227

228
void BaseOutgoingRequest::SetHeader(const string &name, const string &value) {
387✔
229
        headers_[name] = value;
230
}
387✔
231

232
void BaseOutgoingRequest::SetBodyGenerator(BodyGenerator body_gen) {
29✔
233
        async_body_gen_ = nullptr;
29✔
234
        async_body_reader_ = nullptr;
29✔
235
        body_gen_ = body_gen;
29✔
236
}
29✔
237

238
void BaseOutgoingRequest::SetAsyncBodyGenerator(AsyncBodyGenerator body_gen) {
6✔
239
        body_gen_ = nullptr;
6✔
240
        body_reader_ = nullptr;
6✔
241
        async_body_gen_ = body_gen;
6✔
242
}
6✔
243

244
error::Error OutgoingRequest::SetAddress(const string &address) {
176✔
245
        orig_address_ = address;
176✔
246

247
        return BreakDownUrl(address, address_);
176✔
248
}
249

250
IncomingRequest::~IncomingRequest() {
414✔
251
        if (!*cancelled_) {
207✔
252
                stream_.server_.RemoveStream(stream_.shared_from_this());
×
253
        }
254
}
207✔
255

256
void IncomingRequest::Cancel() {
2✔
257
        if (!*cancelled_) {
2✔
258
                stream_.Cancel();
2✔
259
        }
260
}
2✔
261

262
io::ExpectedAsyncReaderPtr IncomingRequest::MakeBodyAsyncReader() {
47✔
263
        if (*cancelled_) {
47✔
264
                return expected::unexpected(MakeError(
×
265
                        StreamCancelledError, "Cannot make reader for a request that doesn't exist anymore"));
×
266
        }
267
        return stream_.server_.MakeBodyAsyncReader(shared_from_this());
94✔
268
}
269

270
void IncomingRequest::SetBodyWriter(io::WriterPtr writer, BodyWriterErrorMode mode) {
35✔
271
        auto exp_reader = MakeBodyAsyncReader();
35✔
272
        if (!exp_reader) {
35✔
273
                if (exp_reader.error().code != MakeError(BodyMissingError, "").code) {
16✔
274
                        log::Error(exp_reader.error().String());
×
275
                }
276
                return;
277
        }
278
        auto &reader = exp_reader.value();
27✔
279

280
        io::AsyncCopy(writer, reader, [reader, mode](error::Error err) {
1,890✔
281
                if (err != error::NoError) {
27✔
282
                        log::Error("Could not copy HTTP stream: " + err.String());
6✔
283
                        if (mode == BodyWriterErrorMode::Cancel) {
3✔
284
                                reader->Cancel();
2✔
285
                        }
286
                }
287
        });
81✔
288
}
289

290
ExpectedOutgoingResponsePtr IncomingRequest::MakeResponse() {
197✔
291
        if (*cancelled_) {
197✔
292
                return expected::unexpected(MakeError(
×
293
                        StreamCancelledError, "Cannot make response for a request that doesn't exist anymore"));
×
294
        }
295
        return stream_.server_.MakeResponse(shared_from_this());
394✔
296
}
297

298
IncomingResponse::IncomingResponse(ClientInterface &client, shared_ptr<bool> cancelled) :
277✔
299
        client_ {client},
300
        cancelled_ {cancelled} {
554✔
301
}
277✔
302

303
void IncomingResponse::Cancel() {
1✔
304
        if (!*cancelled_) {
1✔
305
                client_.Cancel();
1✔
306
        }
307
}
1✔
308

309
io::ExpectedAsyncReaderPtr IncomingResponse::MakeBodyAsyncReader() {
122✔
310
        if (*cancelled_) {
122✔
311
                return expected::unexpected(MakeError(
×
312
                        StreamCancelledError, "Cannot make reader for a response that doesn't exist anymore"));
×
313
        }
314
        return client_.MakeBodyAsyncReader(shared_from_this());
244✔
315
}
316

317
void IncomingResponse::SetBodyWriter(io::WriterPtr writer, BodyWriterErrorMode mode) {
61✔
318
        auto exp_reader = MakeBodyAsyncReader();
61✔
319
        if (!exp_reader) {
61✔
320
                if (exp_reader.error().code != MakeError(BodyMissingError, "").code) {
22✔
321
                        log::Error(exp_reader.error().String());
×
322
                }
323
                return;
324
        }
325
        auto &reader = exp_reader.value();
50✔
326

327
        io::AsyncCopy(writer, reader, [reader, mode](error::Error err) {
3,328✔
328
                if (err != error::NoError) {
31✔
329
                        log::Error("Could not copy HTTP stream: " + err.String());
10✔
330
                        if (mode == BodyWriterErrorMode::Cancel) {
5✔
331
                                reader->Cancel();
4✔
332
                        }
333
                }
334
        });
131✔
335
}
336

337
io::ExpectedAsyncReadWriterPtr IncomingResponse::SwitchProtocol() {
8✔
338
        if (*cancelled_) {
8✔
339
                return expected::unexpected(MakeError(
1✔
340
                        StreamCancelledError, "Cannot switch protocol when the stream doesn't exist anymore"));
3✔
341
        }
342
        return client_.GetHttpClient().SwitchProtocol(shared_from_this());
14✔
343
}
344

345
OutgoingResponse::~OutgoingResponse() {
394✔
346
        if (!*cancelled_) {
197✔
347
                stream_.server_.RemoveStream(stream_.shared_from_this());
4✔
348
        }
349
}
197✔
350

351
void OutgoingResponse::Cancel() {
1✔
352
        if (!*cancelled_) {
1✔
353
                stream_.Cancel();
1✔
354
                stream_.server_.RemoveStream(stream_.shared_from_this());
2✔
355
        }
356
}
1✔
357

358
void OutgoingResponse::SetStatusCodeAndMessage(unsigned code, const string &message) {
195✔
359
        status_code_ = code;
195✔
360
        status_message_ = message;
195✔
361
}
195✔
362

363
void OutgoingResponse::SetHeader(const string &name, const string &value) {
214✔
364
        headers_[name] = value;
365
}
214✔
366

367
void OutgoingResponse::SetBodyReader(io::ReaderPtr body_reader) {
159✔
368
        async_body_reader_ = nullptr;
159✔
369
        body_reader_ = body_reader;
370
}
159✔
371

372
void OutgoingResponse::SetAsyncBodyReader(io::AsyncReaderPtr body_reader) {
4✔
373
        body_reader_ = nullptr;
4✔
374
        async_body_reader_ = body_reader;
375
}
4✔
376

377
error::Error OutgoingResponse::AsyncReply(ReplyFinishedHandler reply_finished_handler) {
186✔
378
        if (*cancelled_) {
186✔
379
                return MakeError(StreamCancelledError, "Cannot reply when response doesn't exist anymore");
2✔
380
        }
381
        return stream_.server_.AsyncReply(shared_from_this(), reply_finished_handler);
555✔
382
}
383

384
error::Error OutgoingResponse::AsyncSwitchProtocol(SwitchProtocolHandler handler) {
9✔
385
        if (*cancelled_) {
9✔
386
                return MakeError(
387
                        StreamCancelledError, "Cannot switch protocol when response doesn't exist anymore");
×
388
        }
389
        return stream_.server_.AsyncSwitchProtocol(shared_from_this(), handler);
27✔
390
}
391

392
ExponentialBackoff::ExpectedInterval ExponentialBackoff::NextInterval() {
117✔
393
        iteration_++;
117✔
394

395
        if (try_count_ > 0 && iteration_ > try_count_) {
117✔
396
                return expected::unexpected(MakeError(MaxRetryError, "Exponential backoff"));
15✔
397
        }
398

399
        chrono::milliseconds current_interval = smallest_interval_;
112✔
400
        // Backoff algorithm: Each interval is returned three times, then it's doubled, and then
401
        // that is returned three times, and so on. But if interval is ever higher than the max
402
        // interval, then return the max interval instead, and once that is returned three times,
403
        // produce MaxRetryError. If try_count_ is set, then that controls the total number of
404
        // retries, but the rest is the same, so then it simply "gets stuck" at max interval for
405
        // many iterations.
406
        for (int count = 3; count < iteration_; count += 3) {
200✔
407
                auto new_interval = current_interval * 2;
408
                if (new_interval > max_interval_) {
409
                        new_interval = max_interval_;
410
                }
411
                if (try_count_ <= 0 && new_interval == current_interval) {
93✔
412
                        return expected::unexpected(MakeError(MaxRetryError, "Exponential backoff"));
15✔
413
                }
414
                current_interval = new_interval;
415
        }
416

417
        return current_interval;
418
}
419

420
} // namespace http
421
} // 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