diff --git a/agent/Makefile.frag b/agent/Makefile.frag index f11dfbf75..7230f5ae3 100644 --- a/agent/Makefile.frag +++ b/agent/Makefile.frag @@ -97,9 +97,10 @@ TEST_BINARIES = \ tests/test_pdo_mysql \ tests/test_pdo_pgsql \ tests/test_pgsql \ - tests/test_php_error \ + tests/test_php_error \ tests/test_php_execute \ tests/test_php_minit \ + tests/test_php_nrini \ tests/test_php_stack \ tests/test_php_stacked_segment \ tests/test_php_wrapper \ diff --git a/agent/php_nrini.c b/agent/php_nrini.c index 2ed62eb10..90c88a531 100644 --- a/agent/php_nrini.c +++ b/agent/php_nrini.c @@ -8,6 +8,7 @@ #include "php_hash.h" #include "php_internal_instrument.h" #include "php_user_instrument.h" +#include "php_nrini.h" #include "nr_commands.h" #include "nr_configstrings.h" @@ -222,6 +223,62 @@ ZEND_DECLARE_MODULE_GLOBALS(newrelic) +char* nr_ini_to_env(const char* ini_name) { + const int NR_INI_PREFIX_LEN = nr_strlen("newrelic."); + char* env_name = NULL; + char* buf = NULL; + char* ini_upper = NULL; + int ini_len = nr_strlen(ini_name); + + // 'newrelic.' is 9 characters - anything less should be rejected. + if (NULL == ini_name || nr_strlen(ini_name) <= NR_INI_PREFIX_LEN) { + return NULL; + } + + // ini value should start with the 'newrelic.' prefix + if (0 != nr_stridx(ini_name, "newrelic.")) { + return NULL; + } + + // algorithm: + // 1. uppercase ini string + // 2. iterate through uppercase ini string and copy each character to the new + // buffer, swapping each '.' with '_'. + // 3. snprintf append the string to 'NEW_RELIC' + // 4. return result + + ini_upper = nr_string_to_uppercase(ini_name); + + buf = (char*)nr_zalloc(ini_len - NR_INI_PREFIX_LEN + 1); + + for (int i = NR_INI_PREFIX_LEN, j = 0; i < nr_strlen(ini_upper); i++) { + if (NR_INI_PREFIX_LEN == i + && ('.' == ini_upper[i] || '_' == ini_upper[i])) { + // skip if the first character is '.' or '_' to avoid double underscores + continue; + } else if ('_' == ini_upper[i] && '_' == ini_upper[i - 1]) { + // skip double '_' characters + continue; + } else if ('.' == ini_upper[i] && '_' != ini_upper[i - 1]) { + // replace a '.' with '_', provided the previous character is not also '_' + buf[j] = '_'; + } else { + // copy the character + buf[j] = ini_upper[i]; + } + j++; + } + + env_name = (char*)nr_zalloc(ini_len + 2); + + snprintf(env_name, ini_len + 2, "%s_%s", "NEW_RELIC", buf); + + nr_free(buf); + nr_free(ini_upper); + + return env_name; +} + typedef void (*foreach_fn_t)(const char* name, int namelen TSRMLS_DC); static void foreach_list(const char* str, foreach_fn_t f_eachname TSRMLS_DC) { @@ -3039,42 +3096,46 @@ STD_PHP_INI_ENTRY_EX("newrelic.application_logging.metrics.enabled", zend_newrelic_globals, newrelic_globals, nr_enabled_disabled_dh) -STD_PHP_INI_ENTRY_EX("newrelic.application_logging.forwarding.context_data.enabled", - "0", - NR_PHP_REQUEST, - nr_boolean_mh, - log_context_data_attributes.enabled, - zend_newrelic_globals, - newrelic_globals, - nr_enabled_disabled_dh) -STD_PHP_INI_ENTRY_EX("newrelic.application_logging.forwarding.context_data.include", - "", - NR_PHP_REQUEST, - nr_string_mh, - log_context_data_attributes.include, - zend_newrelic_globals, - newrelic_globals, - 0) -STD_PHP_INI_ENTRY_EX("newrelic.application_logging.forwarding.context_data.exclude", - "", - NR_PHP_REQUEST, - nr_string_mh, - log_context_data_attributes.exclude, - zend_newrelic_globals, - newrelic_globals, - 0) +STD_PHP_INI_ENTRY_EX( + "newrelic.application_logging.forwarding.context_data.enabled", + "0", + NR_PHP_REQUEST, + nr_boolean_mh, + log_context_data_attributes.enabled, + zend_newrelic_globals, + newrelic_globals, + nr_enabled_disabled_dh) +STD_PHP_INI_ENTRY_EX( + "newrelic.application_logging.forwarding.context_data.include", + "", + NR_PHP_REQUEST, + nr_string_mh, + log_context_data_attributes.include, + zend_newrelic_globals, + newrelic_globals, + 0) +STD_PHP_INI_ENTRY_EX( + "newrelic.application_logging.forwarding.context_data.exclude", + "", + NR_PHP_REQUEST, + nr_string_mh, + log_context_data_attributes.exclude, + zend_newrelic_globals, + newrelic_globals, + 0) /* * Vulnerability Management */ -STD_PHP_INI_ENTRY_EX("newrelic.vulnerability_management.package_detection.enabled", - "1", - NR_PHP_REQUEST, - nr_boolean_mh, - vulnerability_management_package_detection_enabled, - zend_newrelic_globals, - newrelic_globals, - nr_enabled_disabled_dh) +STD_PHP_INI_ENTRY_EX( + "newrelic.vulnerability_management.package_detection.enabled", + "1", + NR_PHP_REQUEST, + nr_boolean_mh, + vulnerability_management_package_detection_enabled, + zend_newrelic_globals, + newrelic_globals, + nr_enabled_disabled_dh) PHP_INI_END() /* } */ diff --git a/agent/php_nrini.h b/agent/php_nrini.h new file mode 100644 index 000000000..a7c4eb13c --- /dev/null +++ b/agent/php_nrini.h @@ -0,0 +1,10 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * This file declares nrini functions. + */ +#ifndef PHP_NRINI_HDR +#define PHP_NRINI_HDR +extern char* nr_ini_to_env(const char* ini_name); +#endif /* PHP_NRINI_HDR */ diff --git a/agent/tests/test_php_nrini.c b/agent/tests/test_php_nrini.c new file mode 100644 index 000000000..78b98d7d5 --- /dev/null +++ b/agent/tests/test_php_nrini.c @@ -0,0 +1,75 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tlib_main.h" +#include "tlib_php.h" + +#include "php_agent.h" +#include "php_nrini.h" + +static void test_nr_ini_to_env(void) { + char* res = NULL; + + res = nr_ini_to_env("newrelic.enabled"); + + tlib_pass_if_str_equal("ini converted to env", "NEW_RELIC_ENABLED", res); + + nr_free(res); + + res = nr_ini_to_env( + "newrelic.application_logging.forwarding.context_data.include"); + + tlib_pass_if_str_equal( + "ini converted to env", + "NEW_RELIC_APPLICATION_LOGGING_FORWARDING_CONTEXT_DATA_INCLUDE", res); + + nr_free(res); + + res = nr_ini_to_env("newrelic.12345"); + + tlib_pass_if_str_equal("numerical values handled correctly", + "NEW_RELIC_12345", res); + + nr_free(res); + + res = nr_ini_to_env("not_a_newrelic.ini_value"); + + tlib_pass_if_null("invalid ini not converted", res); + + nr_free(res); + + res = nr_ini_to_env("newrelic."); + + tlib_pass_if_null("no value after prefix", res); + + nr_free(res); + + res = nr_ini_to_env(NULL); + + tlib_pass_if_null("reject null values", res); + + nr_free(res); + + res = nr_ini_to_env("newrelic.ini__value"); + + tlib_pass_if_str_equal("double underscores handled correctly", + "NEW_RELIC_INI_VALUE", res); + + nr_free(res); + + res = nr_ini_to_env("newrelic._option_"); + + tlib_pass_if_str_equal("dot and underscores handled correctly", + "NEW_RELIC_OPTION_", res); + + nr_free(res); +} + +tlib_parallel_info_t parallel_info + = {.suggested_nthreads = -1, .state_size = 0}; + +void test_main(void* p NRUNUSED) { + test_nr_ini_to_env(); +} diff --git a/axiom/util_strings.c b/axiom/util_strings.c index 54e13e7cf..7c0578ee6 100644 --- a/axiom/util_strings.c +++ b/axiom/util_strings.c @@ -36,6 +36,25 @@ char* nr_string_to_lowercase(const char* str) { return low; } +char* nr_string_to_uppercase(const char* str) { + int i; + char* upper; + + if (NULL == str) { + return NULL; + } + + upper = nr_strdup(str); + if (NULL == upper) { + return NULL; + } + + for (i = 0; upper[i]; i++) { + upper[i] = nr_toupper(upper[i]); + } + return upper; +} + char* nr_formatf(const char* fmt, ...) { int rv; va_list ap; diff --git a/axiom/util_strings.h b/axiom/util_strings.h index 5da515cab..79c8a90e0 100644 --- a/axiom/util_strings.h +++ b/axiom/util_strings.h @@ -32,6 +32,17 @@ */ extern char* nr_string_to_lowercase(const char* str); +/* + * Purpose : Convert a string to upper case, following USASCII rules, returning + * a newly allocated string. + * + * Params : 1. The string to uppercase. + * + * Returns : The newly created string. The caller must arrange to free the + * string. + */ +extern char* nr_string_to_uppercase(const char* str); + /* * Purpose : Wrap asprintf to increase convenience and safety: asprintf behavior * is not in POSIX, and the return string may not be defined in the