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

mendersoftware / mender / 1033213046

11 Oct 2023 12:00PM UTC coverage: 80.166% (+0.1%) from 80.029%
1033213046

push

gitlab-ci

kacf
chore: Make it possible to configure repo for no-build situations.

This still allows installing modules, generators, and Documentation,
but does not need any dependencies to be installed except for the
compiler.

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

6479 of 8082 relevant lines covered (80.17%)

10736.7 hits per line

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

86.41
/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);
340✔
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) {
403✔
93
        const string url_split {"://"};
403✔
94

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

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

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

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

120
                tmp = tmp.substr(split_index + 1);
399✔
121
                auto port = common::StringToLongLong(tmp);
399✔
122
                if (!port) {
399✔
123
                        return error::Error(port.error().code, url + " contains invalid port number");
×
124
                }
125
                address.port = port.value();
399✔
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(
401✔
139
                "URL broken down into (protocol: " + address.protocol + "), (host: " + address.host
802✔
140
                + "), (port: " + to_string(address.port) + "), (path: " + address.path + ")");
1,203✔
141

142
        return error::NoError;
401✔
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) {
164✔
165
        auto prefix_end = prefix.cend();
166
        while (prefix_end != prefix.cbegin() && prefix_end[-1] == '/') {
176✔
167
                prefix_end--;
168
        }
169

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

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

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

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

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

196
Method Request::GetMethod() const {
87✔
197
        return method_;
87✔
198
}
199

200
string Request::GetPath() const {
160✔
201
        return address_.path;
160✔
202
}
203

204
unsigned Response::GetStatusCode() const {
536✔
205
        return status_code_;
536✔
206
}
207

208
string Response::GetStatusMessage() const {
332✔
209
        return status_message_;
332✔
210
}
211

212
void OutgoingRequest::SetMethod(Method method) {
191✔
213
        method_ = method;
191✔
214
}
191✔
215

216
void OutgoingRequest::SetHeader(const string &name, const string &value) {
456✔
217
        headers_[name] = value;
218
}
456✔
219

220
error::Error OutgoingRequest::SetAddress(const string &address) {
191✔
221
        orig_address_ = address;
191✔
222

223
        return BreakDownUrl(address, address_);
191✔
224
}
225

226
void OutgoingRequest::SetBodyGenerator(BodyGenerator body_gen) {
41✔
227
        async_body_gen_ = nullptr;
41✔
228
        async_body_reader_ = nullptr;
41✔
229
        body_gen_ = body_gen;
41✔
230
}
41✔
231

232
void OutgoingRequest::SetAsyncBodyGenerator(AsyncBodyGenerator body_gen) {
6✔
233
        body_gen_ = nullptr;
6✔
234
        body_reader_ = nullptr;
6✔
235
        async_body_gen_ = body_gen;
6✔
236
}
6✔
237

238
IncomingRequest::~IncomingRequest() {
436✔
239
        if (!*cancelled_) {
218✔
240
                stream_.server_.RemoveStream(stream_.shared_from_this());
×
241
        }
242
}
218✔
243

244
void IncomingRequest::Cancel() {
2✔
245
        if (!*cancelled_) {
2✔
246
                stream_.Cancel();
2✔
247
        }
248
}
2✔
249

250
io::ExpectedAsyncReaderPtr IncomingRequest::MakeBodyAsyncReader() {
58✔
251
        if (*cancelled_) {
58✔
252
                return expected::unexpected(MakeError(
×
253
                        StreamCancelledError, "Cannot make reader for a request that doesn't exist anymore"));
×
254
        }
255
        return stream_.server_.MakeBodyAsyncReader(shared_from_this());
116✔
256
}
257

258
void IncomingRequest::SetBodyWriter(io::WriterPtr writer, BodyWriterErrorMode mode) {
46✔
259
        auto exp_reader = MakeBodyAsyncReader();
46✔
260
        if (!exp_reader) {
46✔
261
                if (exp_reader.error().code != MakeError(BodyMissingError, "").code) {
16✔
262
                        log::Error(exp_reader.error().String());
×
263
                }
264
                return;
265
        }
266
        auto &reader = exp_reader.value();
38✔
267

268
        io::AsyncCopy(writer, reader, [reader, mode](error::Error err) {
1,967✔
269
                if (err != error::NoError) {
38✔
270
                        log::Error("Could not copy HTTP stream: " + err.String());
6✔
271
                        if (mode == BodyWriterErrorMode::Cancel) {
3✔
272
                                reader->Cancel();
2✔
273
                        }
274
                }
275
        });
114✔
276
}
277

278
ExpectedOutgoingResponsePtr IncomingRequest::MakeResponse() {
208✔
279
        if (*cancelled_) {
208✔
280
                return expected::unexpected(MakeError(
×
281
                        StreamCancelledError, "Cannot make response for a request that doesn't exist anymore"));
×
282
        }
283
        return stream_.server_.MakeResponse(shared_from_this());
416✔
284
}
285

286
IncomingResponse::IncomingResponse(ClientInterface &client, shared_ptr<bool> cancelled) :
288✔
287
        client_ {client},
288
        cancelled_ {cancelled} {
576✔
289
}
288✔
290

291
void IncomingResponse::Cancel() {
1✔
292
        if (!*cancelled_) {
1✔
293
                client_.Cancel();
1✔
294
        }
295
}
1✔
296

297
io::ExpectedAsyncReaderPtr IncomingResponse::MakeBodyAsyncReader() {
133✔
298
        if (*cancelled_) {
133✔
299
                return expected::unexpected(MakeError(
×
300
                        StreamCancelledError, "Cannot make reader for a response that doesn't exist anymore"));
×
301
        }
302
        return client_.MakeBodyAsyncReader(shared_from_this());
266✔
303
}
304

305
void IncomingResponse::SetBodyWriter(io::WriterPtr writer, BodyWriterErrorMode mode) {
72✔
306
        auto exp_reader = MakeBodyAsyncReader();
72✔
307
        if (!exp_reader) {
72✔
308
                if (exp_reader.error().code != MakeError(BodyMissingError, "").code) {
22✔
309
                        log::Error(exp_reader.error().String());
×
310
                }
311
                return;
312
        }
313
        auto &reader = exp_reader.value();
61✔
314

315
        io::AsyncCopy(writer, reader, [reader, mode](error::Error err) {
3,401✔
316
                if (err != error::NoError) {
42✔
317
                        log::Error("Could not copy HTTP stream: " + err.String());
10✔
318
                        if (mode == BodyWriterErrorMode::Cancel) {
5✔
319
                                reader->Cancel();
4✔
320
                        }
321
                }
322
        });
164✔
323
}
324

325
io::ExpectedAsyncReadWriterPtr IncomingResponse::SwitchProtocol() {
8✔
326
        if (*cancelled_) {
8✔
327
                return expected::unexpected(MakeError(
1✔
328
                        StreamCancelledError, "Cannot switch protocol when the stream doesn't exist anymore"));
3✔
329
        }
330
        return client_.GetHttpClient().SwitchProtocol(shared_from_this());
14✔
331
}
332

333
OutgoingResponse::~OutgoingResponse() {
416✔
334
        if (!*cancelled_) {
208✔
335
                stream_.server_.RemoveStream(stream_.shared_from_this());
4✔
336
        }
337
}
208✔
338

339
void OutgoingResponse::Cancel() {
1✔
340
        if (!*cancelled_) {
1✔
341
                stream_.Cancel();
1✔
342
                stream_.server_.RemoveStream(stream_.shared_from_this());
2✔
343
        }
344
}
1✔
345

346
void OutgoingResponse::SetStatusCodeAndMessage(unsigned code, const string &message) {
206✔
347
        status_code_ = code;
206✔
348
        status_message_ = message;
206✔
349
}
206✔
350

351
void OutgoingResponse::SetHeader(const string &name, const string &value) {
225✔
352
        headers_[name] = value;
353
}
225✔
354

355
void OutgoingResponse::SetBodyReader(io::ReaderPtr body_reader) {
170✔
356
        async_body_reader_ = nullptr;
170✔
357
        body_reader_ = body_reader;
358
}
170✔
359

360
void OutgoingResponse::SetAsyncBodyReader(io::AsyncReaderPtr body_reader) {
4✔
361
        body_reader_ = nullptr;
4✔
362
        async_body_reader_ = body_reader;
363
}
4✔
364

365
error::Error OutgoingResponse::AsyncReply(ReplyFinishedHandler reply_finished_handler) {
197✔
366
        if (*cancelled_) {
197✔
367
                return MakeError(StreamCancelledError, "Cannot reply when response doesn't exist anymore");
2✔
368
        }
369
        return stream_.server_.AsyncReply(shared_from_this(), reply_finished_handler);
588✔
370
}
371

372
error::Error OutgoingResponse::AsyncSwitchProtocol(SwitchProtocolHandler handler) {
9✔
373
        if (*cancelled_) {
9✔
374
                return MakeError(
375
                        StreamCancelledError, "Cannot switch protocol when response doesn't exist anymore");
×
376
        }
377
        return stream_.server_.AsyncSwitchProtocol(shared_from_this(), handler);
27✔
378
}
379

380
ExponentialBackoff::ExpectedInterval ExponentialBackoff::NextInterval() {
117✔
381
        iteration_++;
117✔
382

383
        if (try_count_ > 0 && iteration_ > try_count_) {
117✔
384
                return expected::unexpected(MakeError(MaxRetryError, "Exponential backoff"));
15✔
385
        }
386

387
        chrono::milliseconds current_interval = smallest_interval_;
112✔
388
        // Backoff algorithm: Each interval is returned three times, then it's doubled, and then
389
        // that is returned three times, and so on. But if interval is ever higher than the max
390
        // interval, then return the max interval instead, and once that is returned three times,
391
        // produce MaxRetryError. If try_count_ is set, then that controls the total number of
392
        // retries, but the rest is the same, so then it simply "gets stuck" at max interval for
393
        // many iterations.
394
        for (int count = 3; count < iteration_; count += 3) {
200✔
395
                auto new_interval = current_interval * 2;
396
                if (new_interval > max_interval_) {
397
                        new_interval = max_interval_;
398
                }
399
                if (try_count_ <= 0 && new_interval == current_interval) {
93✔
400
                        return expected::unexpected(MakeError(MaxRetryError, "Exponential backoff"));
15✔
401
                }
402
                current_interval = new_interval;
403
        }
404

405
        return current_interval;
406
}
407

408
} // namespace http
409
} // 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