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

mendersoftware / mender / 2134658426

01 Nov 2025 06:30PM UTC coverage: 75.934% (-0.02%) from 75.957%
2134658426

push

gitlab-ci

web-flow
Merge pull request #1841 from lluiscampos/5.0.x-no-proxy-warning

5.0.x: fix: Added warning for invalid NO_PROXY setting containing comma

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

1 existing line in 1 file now uncovered.

7399 of 9744 relevant lines covered (75.93%)

13917.33 hits per line

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

85.23
/src/client_shared/conf/conf.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 <client_shared/conf.hpp>
16

17
#include <string>
18
#include <cstdlib>
19
#include <cerrno>
20

21
#include <mender-version.h>
22

23
#include <common/error.hpp>
24
#include <common/expected.hpp>
25
#include <common/log.hpp>
26
#include <common/json.hpp>
27

28
namespace mender {
29
namespace client_shared {
30
namespace conf {
31

32
using namespace std;
33
namespace error = mender::common::error;
34
namespace expected = mender::common::expected;
35
namespace log = mender::common::log;
36
namespace json = mender::common::json;
37

38
const string kMenderVersion = MENDER_VERSION;
39

40
const DefaultPathsType DefaultPaths;
41

42
const ConfigErrorCategoryClass ConfigErrorCategory;
43

44
const char *ConfigErrorCategoryClass::name() const noexcept {
×
45
        return "ConfigErrorCategory";
×
46
}
47

48
string ConfigErrorCategoryClass::message(int code) const {
10✔
49
        switch (code) {
10✔
50
        case NoError:
51
                return "Success";
×
52
        case InvalidOptionsError:
53
                return "Invalid options given";
10✔
54
        }
55
        assert(false);
56
        return "Unknown";
×
57
}
58

59
error::Error MakeError(ConfigErrorCode code, const string &msg) {
5✔
60
        return error::Error(error_condition(code, ConfigErrorCategory), msg);
93✔
61
}
62

63

64
string GetEnv(const string &var_name, const string &default_value) {
1,522✔
65
        const char *value = getenv(var_name.c_str());
1,522✔
66
        if (value == nullptr) {
1,522✔
67
                return string(default_value);
1,521✔
68
        } else {
69
                return string(value);
1✔
70
        }
71
}
72

73
ExpectedOptionValue CmdlineOptionsIterator::Next() {
700✔
74
        string option = "";
700✔
75
        string value = "";
700✔
76

77
        if (start_ + pos_ >= end_) {
700✔
78
                return ExpectedOptionValue({"", ""});
216✔
79
        }
80

81
        if (past_double_dash_) {
484✔
82
                OptionValue opt_val {"", start_[pos_]};
6✔
83
                pos_++;
3✔
84
                return ExpectedOptionValue(opt_val);
3✔
85
        }
86

87
        if (start_[pos_] == "--") {
481✔
88
                past_double_dash_ = true;
1✔
89
                pos_++;
1✔
90
                return ExpectedOptionValue({"--", ""});
1✔
91
        }
92

93
        if (start_[pos_][0] == '-') {
480✔
94
                auto eq_idx = start_[pos_].find('=');
208✔
95
                if (eq_idx != string::npos) {
208✔
96
                        option = start_[pos_].substr(0, eq_idx);
3✔
97
                        value = start_[pos_].substr(eq_idx + 1, start_[pos_].size() - eq_idx - 1);
3✔
98
                        pos_++;
3✔
99
                } else {
100
                        option = start_[pos_];
205✔
101
                        pos_++;
205✔
102
                }
103

104
                if (opts_with_value_.count(option) != 0) {
208✔
105
                        // option with value
106
                        if ((value == "") && ((start_ + pos_ >= end_) || (start_[pos_][0] == '-'))) {
170✔
107
                                // the next item is not a value
108
                                error::Error err = MakeError(
109
                                        ConfigErrorCode::InvalidOptionsError, "Option " + option + " missing value");
4✔
110
                                return ExpectedOptionValue(expected::unexpected(err));
4✔
111
                        } else if (value == "") {
168✔
112
                                // only assign the next item as value if there was no value
113
                                // specified as '--opt=value' (parsed above)
114
                                value = start_[pos_];
166✔
115
                                pos_++;
166✔
116
                        }
117
                } else if (opts_wo_value_.count(option) == 0) {
38✔
118
                        // unknown option
119
                        error::Error err = MakeError(
120
                                ConfigErrorCode::InvalidOptionsError, "Unrecognized option '" + option + "'");
48✔
121
                        return ExpectedOptionValue(expected::unexpected(err));
48✔
122
                } else if (value != "") {
14✔
123
                        // option without a value, yet, there was a value specified as '--opt=value' (parsed
124
                        // above)
125
                        error::Error err = MakeError(
126
                                ConfigErrorCode::InvalidOptionsError,
127
                                "Option " + option + " doesn't expect a value");
2✔
128
                        return ExpectedOptionValue(expected::unexpected(err));
2✔
129
                }
130
        } else {
131
                switch (mode_) {
272✔
132
                case ArgumentsMode::AcceptBareArguments:
66✔
133
                        value = start_[pos_];
134
                        pos_++;
66✔
135
                        break;
66✔
136
                case ArgumentsMode::RejectBareArguments:
61✔
137
                        return expected::unexpected(MakeError(
61✔
138
                                ConfigErrorCode::InvalidOptionsError,
139
                                "Unexpected argument '" + start_[pos_] + "'"));
183✔
140
                case ArgumentsMode::StopAtBareArguments:
141
                        return ExpectedOptionValue({"", ""});
145✔
142
                }
143
        }
144

145
        return ExpectedOptionValue({std::move(option), std::move(value)});
247✔
146
}
147

148
expected::ExpectedSize MenderConfig::ProcessCmdlineArgs(
161✔
149
        vector<string>::const_iterator start, vector<string>::const_iterator end, const CliApp &app) {
150
        bool explicit_config_path = false;
151
        bool explicit_fallback_config_path = false;
152
        string log_file = "";
161✔
153
        string log_level;
154
        string trusted_cert;
155
        bool skip_verify_arg = false;
156
        bool version_arg = false;
157
        bool help_arg = false;
158

159
        CmdlineOptionsIterator opts_iter(
160
                start, end, GlobalOptsSetWithValue(), GlobalOptsSetWithoutValue());
483✔
161
        opts_iter.SetArgumentsMode(ArgumentsMode::StopAtBareArguments);
162
        auto ex_opt_val = opts_iter.Next();
161✔
163
        int arg_count = 0;
164
        while (ex_opt_val && ((ex_opt_val.value().option != "") || (ex_opt_val.value().value != ""))) {
310✔
165
                arg_count++;
153✔
166
                auto opt_val = ex_opt_val.value();
153✔
167
                if ((opt_val.option == "--config") || (opt_val.option == "-c")) {
153✔
168
                        paths.SetConfFile(opt_val.value);
169
                        explicit_config_path = true;
170
                } else if ((opt_val.option == "--fallback-config") || (opt_val.option == "-b")) {
149✔
171
                        paths.SetFallbackConfFile(opt_val.value);
172
                        explicit_fallback_config_path = true;
173
                } else if (
174
                        (opt_val.option == "--data") || (opt_val.option == "--datastore")
281✔
175
                        || (opt_val.option == "-d")) {
158✔
176
                        paths.SetDataStore(opt_val.value);
138✔
177
                } else if ((opt_val.option == "--log-file") || (opt_val.option == "-L")) {
10✔
178
                        log_file = opt_val.value;
179
                } else if ((opt_val.option == "--log-level") || (opt_val.option == "-l")) {
10✔
180
                        log_level = opt_val.value;
181
                } else if ((opt_val.option == "--trusted-certs") || (opt_val.option == "-E")) {
8✔
182
                        trusted_cert = opt_val.value;
183
                } else if (opt_val.option == "--skipverify") {
8✔
184
                        skip_verify_arg = true;
185
                } else if ((opt_val.option == "--version") || (opt_val.option == "-v")) {
8✔
186
                        version_arg = true;
187
                } else if ((opt_val.option == "--help") || (opt_val.option == "-h")) {
4✔
188
                        help_arg = true;
189
                        break;
4✔
190
                } else {
191
                        assert(false);
192
                }
193
                ex_opt_val = opts_iter.Next();
298✔
194
        }
195
        if (!ex_opt_val) {
161✔
196
                return expected::unexpected(ex_opt_val.error());
×
197
        }
198

199
        if (version_arg) {
161✔
200
                if (arg_count > 1 || opts_iter.GetPos() < static_cast<size_t>(end - start)) {
4✔
201
                        return expected::unexpected(error::Error(
2✔
202
                                make_error_condition(errc::invalid_argument),
4✔
203
                                "--version can not be combined with other commands and arguments"));
6✔
204
                } else {
205
                        cout << kMenderVersion << endl;
2✔
206
                        return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
6✔
207
                }
208
        }
209

210
        if (help_arg) {
157✔
211
                PrintCliHelp(app);
4✔
212
                return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
12✔
213
        }
214

215
        if (log_file != "") {
153✔
216
                auto err = log::SetupFileLogging(log_file, true);
×
217
                if (error::NoError != err) {
×
218
                        return expected::unexpected(err);
×
219
                }
220
        }
221

222
        SetLevel(log::kDefaultLogLevel);
153✔
223

224
        if (log_level != "") {
153✔
225
                auto ex_log_level = log::StringToLogLevel(log_level);
2✔
226
                if (!ex_log_level) {
2✔
227
                        return expected::unexpected(ex_log_level.error());
×
228
                }
229
                SetLevel(ex_log_level.value());
2✔
230
        }
231

232
        auto err = LoadConfigFile_(paths.GetFallbackConfFile(), explicit_fallback_config_path);
306✔
233
        if (error::NoError != err) {
153✔
234
                this->Reset();
×
235
                return expected::unexpected(err);
×
236
        }
237

238
        err = LoadConfigFile_(paths.GetConfFile(), explicit_config_path);
306✔
239
        if (error::NoError != err) {
153✔
240
                this->Reset();
×
241
                return expected::unexpected(err);
×
242
        }
243

244
        if (this->update_log_path != "") {
153✔
245
                paths.SetUpdateLogPath(this->update_log_path);
246
        }
247

248
        if (log_level == "" && this->daemon_log_level != "") {
153✔
249
                auto ex_log_level = log::StringToLogLevel(this->daemon_log_level);
1✔
250
                if (!ex_log_level) {
1✔
251
                        return expected::unexpected(ex_log_level.error());
×
252
                }
253
                SetLevel(ex_log_level.value());
1✔
254
        }
255

256
        if (trusted_cert != "") {
153✔
257
                this->server_certificate = trusted_cert;
×
258
        }
259

260
        if (skip_verify_arg) {
153✔
261
                this->skip_verify = true;
×
262
        }
263

264
        http_client_config_.server_cert_path = server_certificate;
153✔
265
        http_client_config_.client_cert_path = https_client.certificate;
153✔
266
        http_client_config_.client_cert_key_path = https_client.key;
153✔
267
        http_client_config_.ssl_engine = https_client.ssl_engine;
153✔
268
        http_client_config_.skip_verify = skip_verify;
153✔
269

270
        auto proxy = http::GetHttpProxyStringFromEnvironment();
153✔
271
        if (proxy) {
153✔
272
                http_client_config_.http_proxy = proxy.value();
152✔
273
        } else {
274
                return expected::unexpected(proxy.error());
2✔
275
        }
276

277
        proxy = http::GetHttpsProxyStringFromEnvironment();
304✔
278
        if (proxy) {
152✔
279
                http_client_config_.https_proxy = proxy.value();
151✔
280
        } else {
281
                return expected::unexpected(proxy.error());
2✔
282
        }
283

284
        proxy = http::GetNoProxyStringFromEnvironment();
302✔
285
        if (proxy) {
151✔
286
                http_client_config_.no_proxy = proxy.value();
150✔
287
        } else {
288
                return expected::unexpected(proxy.error());
2✔
289
        }
290

291
        if (http_client_config_.no_proxy.find(',') != string::npos) {
150✔
NEW
292
                log::Warning(
×
NEW
293
                        "Unsupported syntax for NO_PROXY environment variable. Use space-separated values instead of comma-separated.");
×
294
        }
295

296
        return opts_iter.GetPos();
297
}
298

299
error::Error MenderConfig::LoadConfigFile_(const string &path, bool required) {
306✔
300
        auto ret = this->LoadFile(path);
306✔
301
        if (!ret) {
306✔
302
                if (required) {
301✔
303
                        // any failure when a file is required (e.g. path was given explicitly) means an error
304
                        log::Error("Failed to load config from '" + path + "': " + ret.error().message);
×
305
                        return ret.error();
×
306
                } else if (ret.error().IsErrno(ENOENT)) {
301✔
307
                        // File doesn't exist, OK for non-required
308
                        log::Debug("Failed to load config from '" + path + "': " + ret.error().message);
602✔
309
                        return error::NoError;
301✔
310
                } else {
311
                        // other errors (parsing errors,...) for default paths should produce warnings
312
                        log::Warning("Failed to load config from '" + path + "': " + ret.error().message);
×
313
                        return error::NoError;
×
314
                }
315
        }
316
        // else
317

318
        return error::NoError;
5✔
319
}
320

321
} // namespace conf
322
} // namespace client_shared
323
} // 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