diff --git a/ext/configuration.h b/ext/configuration.h index 1208433253..ee234fd0e5 100644 --- a/ext/configuration.h +++ b/ext/configuration.h @@ -156,7 +156,7 @@ enum ddtrace_sampling_rules_format { CONFIG(CUSTOM(INT), DD_TRACE_SAMPLING_RULES_FORMAT, "glob", .parser = dd_parse_sampling_rules_format) \ CONFIG(JSON, DD_SPAN_SAMPLING_RULES, "[]") \ CONFIG(STRING, DD_SPAN_SAMPLING_RULES_FILE, "", .ini_change = ddtrace_alter_sampling_rules_file_config) \ - CONFIG(SET_LOWERCASE, DD_TRACE_HEADER_TAGS, "", .ini_change = ddtrace_alter_DD_TRACE_HEADER_TAGS) \ + CONFIG(SET_OR_MAP_LOWERCASE, DD_TRACE_HEADER_TAGS, "", .ini_change = ddtrace_alter_DD_TRACE_HEADER_TAGS) \ CONFIG(INT, DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH, "512") \ CONFIG(MAP, DD_TRACE_PEER_SERVICE_MAPPING, "") \ CONFIG(BOOL, DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED, "false") \ @@ -274,6 +274,7 @@ typedef enum { DD_CONFIGURATION } ddtrace_config_id; } #define SET MAP #define SET_LOWERCASE MAP +#define SET_OR_MAP_LOWERCASE MAP #define JSON MAP #define MAP(id) \ static inline zend_array *get_##id(void) { return Z_ARR_P(zai_config_get_value(DDTRACE_CONFIG_##id)); } \ @@ -294,6 +295,7 @@ static inline bool get_global_DD_TRACE_SIDECAR_TRACE_SENDER(void) { return true; #undef MAP #undef SET #undef SET_LOWERCASE +#undef SET_OR_MAP_LOWERCASE #undef JSON #undef BOOL #undef INT diff --git a/ext/serializer.c b/ext/serializer.c index 173a87dd3b..328e140ed8 100644 --- a/ext/serializer.c +++ b/ext/serializer.c @@ -324,14 +324,20 @@ static zend_result dd_add_meta_array(void *context, ddtrace_string key, ddtrace_ static void dd_add_header_to_meta(zend_array *meta, const char *type, zend_string *lowerheader, zend_string *headerval) { - if (zend_hash_exists(get_DD_TRACE_HEADER_TAGS(), lowerheader)) { - for (char *ptr = ZSTR_VAL(lowerheader); *ptr; ++ptr) { - if ((*ptr < 'a' || *ptr > 'z') && *ptr != '-' && (*ptr < '0' || *ptr > '9')) { - *ptr = '_'; + zval *header_config = zend_hash_find(get_DD_TRACE_HEADER_TAGS(), lowerheader); + if (header_config != NULL && Z_TYPE_P(header_config) == IS_STRING) { + zend_string *header_config_str = Z_STR_P(header_config); + zend_string *headertag; + if (ZSTR_LEN(header_config_str) == 0) { + for (char *ptr = ZSTR_VAL(lowerheader); *ptr; ++ptr) { + if ((*ptr < 'a' || *ptr > 'z') && *ptr != '-' && (*ptr < '0' || *ptr > '9')) { + *ptr = '_'; + } } + headertag = zend_strpprintf(0, "http.%s.headers.%s", type, ZSTR_VAL(lowerheader)); + } else { + headertag = zend_string_copy(header_config_str); } - - zend_string *headertag = zend_strpprintf(0, "http.%s.headers.%s", type, ZSTR_VAL(lowerheader)); zval headerzv; ZVAL_STR_COPY(&headerzv, headerval); zend_hash_update(meta, headertag, &headerzv); diff --git a/tests/Unit/ConfigurationTest.php b/tests/Unit/ConfigurationTest.php index 6fb10e4568..c96ceaacb3 100644 --- a/tests/Unit/ConfigurationTest.php +++ b/tests/Unit/ConfigurationTest.php @@ -279,10 +279,9 @@ public function testHttpHeadersCanSetOne() public function testHttpHeadersCanSetMultiple() { $this->putEnvAndReloadConfig([ - 'DD_TRACE_HEADER_TAGS=A-Header ,Any-Name , cOn7aining-!spe_cial?:ch/ars ', + 'DD_TRACE_HEADER_TAGS=A-Header ,Any-Name , cOn7aining-!spe_cial?:ch/ars , valueless:, Some-Header:with-colon-Key', ]); - // Same behavior as python tracer: - // https://github.com/DataDog/dd-trace-py/blob/f1298cb8100f146059f978b58c88641bd7424af8/ddtrace/http/headers.py - $this->assertSame(['a-header', 'any-name', 'con7aining-!spe_cial?:ch/ars'], array_keys(\dd_trace_env_config("DD_TRACE_HEADER_TAGS"))); + $this->assertSame(['a-header', 'any-name', 'con7aining-!spe_cial?', 'valueless', 'some-header'], array_keys(\dd_trace_env_config("DD_TRACE_HEADER_TAGS"))); + $this->assertEquals(['a-header' => '', 'any-name' => '', 'con7aining-!spe_cial?' => 'ch/ars', 'valueless' => '', 'some-header' => 'with-colon-Key'], \dd_trace_env_config("DD_TRACE_HEADER_TAGS")); } } diff --git a/tests/ext/dd_trace_serialize_header_to_meta.phpt b/tests/ext/dd_trace_serialize_header_to_meta.phpt new file mode 100644 index 0000000000..f8e548765a --- /dev/null +++ b/tests/ext/dd_trace_serialize_header_to_meta.phpt @@ -0,0 +1,26 @@ +--TEST-- +Headers values are mapped to expected tag key +--ENV-- +HTTP_CONTENT_TYPE=text/plain +HTTP_CUSTOM_HEADER=custom-header-value +HTTP_HEADER1=val +HTTP_HEADER2=v a l +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_HEADER_TAGS=Content-Type,Custom-Header:custom-HeaderKey,header1: t a g ,header2:tag +--GET-- +application_key=123 +--FILE-- + +--EXPECT-- +string(10) "text/plain" +string(19) "custom-header-value" +string(3) "val" +string(5) "v a l" diff --git a/zend_abstract_interface/config/config_decode.c b/zend_abstract_interface/config/config_decode.c index 6daa3bb5f0..552360f657 100644 --- a/zend_abstract_interface/config/config_decode.c +++ b/zend_abstract_interface/config/config_decode.c @@ -105,7 +105,7 @@ static bool zai_config_decode_int(zai_str value, zval *decoded_value) { return true; } -static bool zai_config_decode_map(zai_str value, zval *decoded_value, bool persistent) { +static bool zai_config_decode_map(zai_str value, zval *decoded_value, bool persistent, bool lowercase, bool map_keyless) { zval tmp; ZVAL_ARR(&tmp, pemalloc(sizeof(HashTable), persistent)); zend_hash_init(Z_ARRVAL(tmp), 8, NULL, persistent ? ZVAL_INTERNAL_PTR_DTOR : ZVAL_PTR_DTOR, persistent); @@ -113,41 +113,90 @@ static bool zai_config_decode_map(zai_str value, zval *decoded_value, bool persi char *data = (char *)value.ptr; if (data && *data) { // non-empty const char *key_start, *key_end, *value_start, *value_end; + do { - if (*data != ',' && *data != ' ' && *data != '\t' && *data != '\n') { - key_start = key_end = data; - while (*++data) { + while (*data == ',' || *data == ' ' || *data == '\t' || *data == '\n') { + data++; + } + + if (*data) { + key_start = data; + key_end = NULL; + bool has_colon = false; + + while (*data && *data != ',') { if (*data == ':') { - while (*++data && (*data == ' ' || *data == '\t' || *data == '\n')) - ; + has_colon = true; + data++; + + while (*data == ' ' || *data == '\t' || *data == '\n') data++; - value_start = value_end = data; - if (!*data || *data == ',') { - --value_end; // empty string instead of single char + if (*data == ',' || !*data) { + value_end = NULL; } else { - while (*++data && *data != ',') { + value_start = value_end = data; + do { if (*data != ' ' && *data != '\t' && *data != '\n') { value_end = data; } + data++; + } while (*data && *data != ','); + } + + if (key_end && key_start) { + size_t key_len = key_end - key_start + 1; + + zend_string *key = zend_string_init(key_start, key_len, persistent); + if (lowercase) { + zend_str_tolower(ZSTR_VAL(key), ZSTR_LEN(key)); + } + + zval val; + if (value_end) { + size_t value_len = value_end - value_start + 1; + ZVAL_NEW_STR(&val, zend_string_init(value_start, value_len, persistent)); + } else { + if (persistent) { + ZVAL_EMPTY_PSTRING(&val); + } else { + ZVAL_EMPTY_STRING(&val); + } } + zend_hash_update(Z_ARRVAL(tmp), key, &val); + zend_string_release(key); } - size_t key_len = key_end - key_start + 1; - size_t value_len = value_end - value_start + 1; - zval val; - ZVAL_NEW_STR(&val, zend_string_init(value_start, value_len, persistent)); - zend_hash_str_update(Z_ARRVAL(tmp), key_start, key_len, &val); break; } + + // Set key_end to the last valid non-whitespace character of the key if (*data != ' ' && *data != '\t' && *data != '\n') { key_end = data; } + data++; + } + + // Handle standalone keys (without a colon) if map_keyless is enabled + if (map_keyless && !has_colon && key_end) { + size_t key_len = key_end - key_start + 1; + zend_string *key = zend_string_init(key_start, key_len, persistent); + if (lowercase) { + zend_str_tolower(ZSTR_VAL(key), ZSTR_LEN(key)); + } + + zval val; + if (persistent) { + ZVAL_EMPTY_PSTRING(&val); + } else { + ZVAL_EMPTY_STRING(&val); + } + zend_hash_update(Z_ARRVAL(tmp), key, &val); + zend_string_release(key); } - } else { - ++data; } } while (*data); + // Check if the array has any elements; if not, cleanup if (zend_hash_num_elements(Z_ARRVAL(tmp)) == 0) { zend_hash_destroy(Z_ARRVAL(tmp)); pefree(Z_ARRVAL(tmp), persistent); @@ -232,7 +281,9 @@ bool zai_config_decode_value(zai_str value, zai_config_type type, zai_custom_par case ZAI_CONFIG_TYPE_INT: return zai_config_decode_int(value, decoded_value); case ZAI_CONFIG_TYPE_MAP: - return zai_config_decode_map(value, decoded_value, persistent); + return zai_config_decode_map(value, decoded_value, persistent, false, false); + case ZAI_CONFIG_TYPE_SET_OR_MAP_LOWERCASE: + return zai_config_decode_map(value, decoded_value, persistent, true, true); case ZAI_CONFIG_TYPE_SET: return zai_config_decode_set(value, decoded_value, persistent, false); case ZAI_CONFIG_TYPE_SET_LOWERCASE: diff --git a/zend_abstract_interface/config/config_decode.h b/zend_abstract_interface/config/config_decode.h index 754bb43a01..c811b37df2 100644 --- a/zend_abstract_interface/config/config_decode.h +++ b/zend_abstract_interface/config/config_decode.h @@ -13,6 +13,7 @@ typedef enum { ZAI_CONFIG_TYPE_MAP, ZAI_CONFIG_TYPE_SET, ZAI_CONFIG_TYPE_SET_LOWERCASE, + ZAI_CONFIG_TYPE_SET_OR_MAP_LOWERCASE, ZAI_CONFIG_TYPE_JSON, ZAI_CONFIG_TYPE_STRING, ZAI_CONFIG_TYPE_CUSTOM,