diff --git a/clients/cli/common.c b/clients/cli/common.c
index 2d7ecc6811..22e750475b 100644
--- a/clients/cli/common.c
+++ b/clients/cli/common.c
@@ -826,10 +826,11 @@ nmc_bond_validate_mode (const char *mode, GError **error)
/*
* nmc_team_check_config:
* @config: file name with team config, or raw team JSON config data
- * @out_config: raw team JSON config data (with removed new-line characters)
+ * @out_config: raw team JSON config data
+ * The value must be freed with g_free().
* @error: location to store error, or %NUL
*
- * Check team config from @config parameter and return the checked/sanitized
+ * Check team config from @config parameter and return the checked
* config in @out_config.
*
* Returns: %TRUE if the config is valid, %FALSE if it is invalid
@@ -837,33 +838,58 @@ nmc_bond_validate_mode (const char *mode, GError **error)
gboolean
nmc_team_check_config (const char *config, char **out_config, GError **error)
{
- char *contents = NULL;
+ enum {
+ _TEAM_CONFIG_TYPE_GUESS,
+ _TEAM_CONFIG_TYPE_FILE,
+ _TEAM_CONFIG_TYPE_JSON,
+ } desired_type = _TEAM_CONFIG_TYPE_GUESS;
+ const char *filename = NULL;
size_t c_len = 0;
+ gs_free char *config_clone = NULL;
*out_config = NULL;
- if (!config || strlen (config) == strspn (config, " \t"))
+ if (!config || !config[0])
return TRUE;
- /* 'config' can be either a file name or raw JSON config data */
- if (g_file_test (config, G_FILE_TEST_EXISTS))
- (void) g_file_get_contents (config, &contents, NULL, NULL);
- else
- contents = g_strdup (config);
-
- if (contents) {
- g_strstrip (contents);
- c_len = strlen (contents);
+ if (g_str_has_prefix (config, "file://")) {
+ config += NM_STRLEN ("file://");
+ desired_type = _TEAM_CONFIG_TYPE_FILE;
+ } else if (g_str_has_prefix (config, "json://")) {
+ config += NM_STRLEN ("json://");
+ desired_type = _TEAM_CONFIG_TYPE_JSON;
}
- /* Do a simple validity check */
- if (!contents || !contents[0] || c_len > 100000 || contents[0] != '{' || contents[c_len-1] != '}') {
- g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
- _("'%s' is not a valid team configuration or file name."), config);
- g_free (contents);
+ if (NM_IN_SET (desired_type, _TEAM_CONFIG_TYPE_FILE, _TEAM_CONFIG_TYPE_GUESS)) {
+ gs_free char *contents = NULL;
+
+ if (!g_file_get_contents (config, &contents, &c_len, NULL)) {
+ if (desired_type == _TEAM_CONFIG_TYPE_FILE) {
+ g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
+ _("cannot read team config from file '%s'"),
+ config);
+ return FALSE;
+ }
+ } else {
+ filename = config;
+ config = config_clone = g_steal_pointer (&contents);
+ }
+ }
+
+ if (!nm_utils_is_json_object (config, NULL)) {
+ if (filename) {
+ g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
+ _("'%s' does not contain a valid team configuration"), filename);
+ } else {
+ g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
+ _("team configuration must be a JSON object"));
+ }
return FALSE;
}
- *out_config = g_strdelimit (contents, "\r\n", ' ');
+
+ *out_config = (config == config_clone)
+ ? g_steal_pointer (&config_clone)
+ : g_strdup (config);
return TRUE;
}
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index 471353e741..2a1c69d94b 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -329,7 +329,6 @@ gboolean _nm_utils_inet6_is_token (const struct in6_addr *in6addr);
/***********************************************************/
-gboolean _nm_utils_check_valid_json (const char *json, GError **error);
gboolean _nm_utils_team_config_equal (const char *conf1, const char *conf2, gboolean port);
#endif
diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c
index 16f0c225b9..f25fa0e1a4 100644
--- a/libnm-core/nm-keyfile-reader.c
+++ b/libnm-core/nm-keyfile-reader.c
@@ -1175,7 +1175,7 @@ team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key
gs_free_error GError *error = NULL;
conf = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
- if (conf && conf[0] && !_nm_utils_check_valid_json (conf, &error)) {
+ if (conf && conf[0] && !nm_utils_is_json_object (conf, &error)) {
handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
_("ignoring invalid team configuration: %s"),
error->message);
diff --git a/libnm-core/nm-setting-team-port.c b/libnm-core/nm-setting-team-port.c
index e3ddbdad7c..f64aa5f947 100644
--- a/libnm-core/nm-setting-team-port.c
+++ b/libnm-core/nm-setting-team-port.c
@@ -117,7 +117,17 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
}
if (priv->config) {
- if (!_nm_utils_check_valid_json (priv->config, error)) {
+ if (strlen (priv->config) > 1*1024*1024) {
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("team config exceeds size limit"));
+ g_prefix_error (error,
+ "%s.%s: ",
+ NM_SETTING_TEAM_PORT_SETTING_NAME,
+ NM_SETTING_TEAM_PORT_CONFIG);
+ return FALSE;
+ }
+
+ if (!nm_utils_is_json_object (priv->config, error)) {
g_prefix_error (error,
"%s.%s: ",
NM_SETTING_TEAM_PORT_SETTING_NAME,
diff --git a/libnm-core/nm-setting-team.c b/libnm-core/nm-setting-team.c
index 5bf11ed921..e83ce309a4 100644
--- a/libnm-core/nm-setting-team.c
+++ b/libnm-core/nm-setting-team.c
@@ -89,7 +89,17 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
return FALSE;
if (priv->config) {
- if (!_nm_utils_check_valid_json (priv->config, error)) {
+ if (strlen (priv->config) > 1*1024*1024) {
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("team config exceeds size limit"));
+ g_prefix_error (error,
+ "%s.%s: ",
+ NM_SETTING_TEAM_SETTING_NAME,
+ NM_SETTING_TEAM_CONFIG);
+ return FALSE;
+ }
+
+ if (!nm_utils_is_json_object (priv->config, error)) {
g_prefix_error (error,
"%s.%s: ",
NM_SETTING_TEAM_SETTING_NAME,
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index 818b0c43ec..cd54ba6b5f 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -4407,8 +4407,20 @@ const char **nm_utils_enum_get_values (GType type, gint from, gint to)
}
#if WITH_JANSSON
+/**
+ * nm_utils_is_json_object:
+ * @str: the JSON string to test
+ * @error: optional error reason
+ *
+ * Returns: whether the passed string is valid JSON.
+ * If libnm is not compiled with libjansson support, this check will
+ * also return %TRUE for possibly invalid inputs. If that is a problem
+ * for you, you must validate the JSON yourself.
+ *
+ * Since: 1.6
+ */
gboolean
-_nm_utils_check_valid_json (const char *str, GError **error)
+nm_utils_is_json_object (const char *str, GError **error)
{
json_t *json;
json_error_t jerror;
@@ -4419,7 +4431,7 @@ _nm_utils_check_valid_json (const char *str, GError **error)
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
- "value is NULL or empty");
+ str ? _("value is NULL") : _("value is empty"));
return FALSE;
}
@@ -4428,9 +4440,19 @@ _nm_utils_check_valid_json (const char *str, GError **error)
g_set_error (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
- "%s at position %d",
- jerror.text,
- jerror.position);
+ _("invalid JSON at position %d (%s)"),
+ jerror.position,
+ jerror.text);
+ return FALSE;
+ }
+
+ /* valid JSON (depending on the definition) can also be a literal.
+ * Here we only allow objects. */
+ if (!json_is_object (json)) {
+ g_set_error_literal (error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("is not a JSON object"));
return FALSE;
}
@@ -4522,17 +4544,48 @@ out:
#else /* WITH_JANSSON */
gboolean
-_nm_utils_check_valid_json (const char *str, GError **error)
+nm_utils_is_json_object (const char *str, GError **error)
{
+ g_return_val_if_fail (!error || !*error, FALSE);
+
+ if (str) {
+ /* libjansson also requires only utf-8 encoding. */
+ if (!g_utf8_validate (str, -1, NULL)) {
+ g_set_error_literal (error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("not valid utf-8"));
+ return FALSE;
+ }
+ while (g_ascii_isspace (str[0]))
+ str++;
+ }
+
if (!str || !str[0]) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
- "value is NULL or empty");
+ str ? _("value is NULL") : _("value is empty"));
return FALSE;
}
- return TRUE;
+ /* do some very basic validation to see if this might be a JSON object. */
+ if (str[0] == '{') {
+ g_size l;
+
+ l = strlen (str) - 1;
+ while (l > 0 && g_ascii_isspace (str[l]))
+ l--;
+
+ if (str[l] == '}')
+ return TRUE;
+ }
+
+ g_set_error_literal (error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("is not a JSON object"));
+ return FALSE;
}
gboolean
diff --git a/libnm-core/nm-utils.h b/libnm-core/nm-utils.h
index 407c14e2c4..4fabfdb7b7 100644
--- a/libnm-core/nm-utils.h
+++ b/libnm-core/nm-utils.h
@@ -90,6 +90,9 @@ gboolean nm_utils_ap_mode_security_valid (NMUtilsSecurityType type,
gboolean nm_utils_wep_key_valid (const char *key, NMWepKeyType wep_type);
gboolean nm_utils_wpa_psk_valid (const char *psk);
+NM_AVAILABLE_IN_1_6
+gboolean nm_utils_is_json_object (const char *json, GError **error);
+
GVariant *nm_utils_ip4_dns_to_variant (char **dns);
char **nm_utils_ip4_dns_from_variant (GVariant *value);
GVariant *nm_utils_ip4_addresses_to_variant (GPtrArray *addresses,
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index e8a9404bf3..c75abbf999 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -4685,7 +4685,7 @@ _json_config_check_valid (const char *conf, gboolean expected)
gs_free_error GError *error = NULL;
gboolean res;
- res = _nm_utils_check_valid_json (conf, &error);
+ res = nm_utils_is_json_object (conf, &error);
g_assert_cmpint (res, ==, expected);
g_assert (res || error);
}
diff --git a/libnm/libnm.ver b/libnm/libnm.ver
index 6c149f6587..bc47e8625d 100644
--- a/libnm/libnm.ver
+++ b/libnm/libnm.ver
@@ -1086,4 +1086,5 @@ global:
libnm_1_6_0 {
nm_capability_get_type;
+ nm_utils_is_json_object;
} libnm_1_4_0;
diff --git a/man/nmcli.xml b/man/nmcli.xml
index 5a936335c0..965c05f955 100644
--- a/man/nmcli.xml
+++ b/man/nmcli.xml
@@ -1827,21 +1827,25 @@ It's equivalent of using the