Hi All. Here attached you may find a new set of patches related to WURFL module that should address Christopher and Willy suggestion.
Thank you in advance for a feedback Regards -Massimiliano -- Massimiliano Bellomi Senior Software Engineer Scientiamobile Italy - [email protected] +39 338 6990288 Milano Office : +39 02 620227260 skype: massimiliano.bellomi
From cb913efa7c2fb5ebea8bea480dc7f9449b520a82 Mon Sep 17 00:00:00 2001 From: mbellomi <[email protected]> Date: Tue, 21 May 2019 15:51:46 +0200 Subject: [PATCH 3/8] MINOR WURFL fixed Engine load failed error when wurfl-information-list contains wurfl_root_id --- src/wurfl.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/wurfl.c b/src/wurfl.c index 7167a929..d5c85add 100644 --- a/src/wurfl.c +++ b/src/wurfl.c @@ -102,9 +102,9 @@ static const struct { {"wurfl_isdevroot", ha_wurfl_get_wurfl_isdevroot}, {"wurfl_last_load_time", ha_wurfl_get_wurfl_last_load_time}, {"wurfl_normalized_useragent", ha_wurfl_get_wurfl_normalized_useragent}, + {"wurfl_root_id", ha_wurfl_get_wurfl_root_id}, {"wurfl_useragent", ha_wurfl_get_wurfl_useragent}, {"wurfl_useragent_priority", ha_wurfl_get_wurfl_useragent_priority }, // kept for backward conf file compat - {"wurfl_root_id", ha_wurfl_get_wurfl_root_id}, }; static const int HA_WURFL_PROPERTIES_NBR = 10; @@ -602,7 +602,10 @@ INITCALL1(STG_REGISTER, sample_register_convs, &conv_kws); // WURFL properties wrapper functions static const char *ha_wurfl_get_wurfl_root_id (wurfl_handle wHandle, wurfl_device_handle dHandle) { - return wurfl_device_get_root_id(dHandle); + if (wurfl_device_get_root_id(dHandle)) + return wurfl_device_get_root_id(dHandle); + else + return ""; } static const char *ha_wurfl_get_wurfl_id (wurfl_handle wHandle, wurfl_device_handle dHandle) -- 2.17.1
From ed250908acdaf37133f429dd9b38534788ada079 Mon Sep 17 00:00:00 2001 From: mbellomi <[email protected]> Date: Tue, 21 May 2019 16:11:42 +0200 Subject: [PATCH 4/8] MINOR WURFL shows log messages during module initialization. Now some useful startup information is logged to stderr --- src/wurfl.c | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/wurfl.c b/src/wurfl.c index d5c85add..0d03efa6 100644 --- a/src/wurfl.c +++ b/src/wurfl.c @@ -258,39 +258,35 @@ static int ha_wurfl_init(void) int wurfl_result_code = WURFL_OK; int len; - send_log(NULL, LOG_NOTICE, "WURFL: Loading module v.%s\n", HA_WURFL_MODULE_VERSION); + ha_notice("WURFL: Loading module v.%s\n", HA_WURFL_MODULE_VERSION); // creating WURFL handler global_wurfl.handle = wurfl_create(); if (global_wurfl.handle == NULL) { - ha_warning("WURFL: Engine handler creation failed"); - send_log(NULL, LOG_WARNING, "WURFL: Engine handler creation failed\n"); + ha_warning("WURFL: Engine handler creation failed\n"); return ERR_WARN; } - send_log(NULL, LOG_NOTICE, "WURFL: Engine handler created - API version %s\n", wurfl_get_api_version() ); + ha_notice("WURFL: Engine handler created - API version %s\n", wurfl_get_api_version() ); // set wurfl data file if (global_wurfl.data_file == NULL) { ha_warning("WURFL: missing wurfl-data-file parameter in global configuration\n"); - send_log(NULL, LOG_WARNING, "WURFL: missing wurfl-data-file parameter in global configuration\n"); return ERR_WARN; } if (wurfl_set_root(global_wurfl.handle, global_wurfl.data_file) != WURFL_OK) { ha_warning("WURFL: Engine setting root file failed - %s\n", wurfl_get_error_message(global_wurfl.handle)); - send_log(NULL, LOG_WARNING, "WURFL: Engine setting root file failed - %s\n", wurfl_get_error_message(global_wurfl.handle)); return ERR_WARN; } - send_log(NULL, LOG_NOTICE, "WURFL: Engine root file set to %s\n", global_wurfl.data_file); + ha_notice("WURFL: Engine root file set to %s\n", global_wurfl.data_file); // just a log to inform which separator char has to be used - send_log(NULL, LOG_NOTICE, "WURFL: Information list separator set to '%c'\n", global_wurfl.information_list_separator); + ha_notice("WURFL: Information list separator set to '%c'\n", global_wurfl.information_list_separator); // load wurfl data needed ( and filter whose are supposed to be capabilities ) if (LIST_ISEMPTY(&global_wurfl.information_list)) { ha_warning("WURFL: missing wurfl-information-list parameter in global configuration\n"); - send_log(NULL, LOG_WARNING, "WURFL: missing wurfl-information-list parameter in global configuration\n"); return ERR_WARN; } else { // ebtree initialization @@ -302,21 +298,24 @@ static int ha_wurfl_init(void) if (ebst_lookup(&global_wurfl.btree, wi->data.name) == NULL) { if ((wi->data.func_callback = (PROP_CALLBACK_FUNC) ha_wurfl_get_property_callback(wi->data.name)) != NULL) { wi->data.type = HA_WURFL_DATA_TYPE_PROPERTY; - ha_wurfl_log("WURFL: [%s] is a valid wurfl data [property]\n",wi->data.name); +#ifdef WURFL_DEBUG + ha_notice("WURFL: [%s] is a valid wurfl data [property]\n",wi->data.name); +#endif } else if (wurfl_has_virtual_capability(global_wurfl.handle, wi->data.name)) { wi->data.type = HA_WURFL_DATA_TYPE_VCAP; - ha_wurfl_log("WURFL: [%s] is a valid wurfl data [virtual capability]\n",wi->data.name); +#ifdef WURFL_DEBUG + ha_notice("WURFL: [%s] is a valid wurfl data [virtual capability]\n",wi->data.name); +#endif } else { // by default a cap type is assumed to be and we control it on engine load wi->data.type = HA_WURFL_DATA_TYPE_CAP; if (wurfl_add_requested_capability(global_wurfl.handle, wi->data.name) != WURFL_OK) { ha_warning("WURFL: capability filtering failed - %s\n", wurfl_get_error_message(global_wurfl.handle)); - send_log(NULL, LOG_WARNING, "WURFL: capability filtering failed - %s\n", wurfl_get_error_message(global_wurfl.handle)); return ERR_WARN; } - ha_wurfl_log("WURFL: [%s] treated as wurfl capability. Will check its validity later, on engine load\n",wi->data.name); + ha_notice("WURFL: [%s] treated as wurfl capability. Will check its validity later, on engine load\n",wi->data.name); } // ebtree insert here @@ -326,7 +325,6 @@ static int ha_wurfl_init(void) if (wn == NULL) { ha_warning("WURFL: Error allocating memory for information tree element.\n"); - send_log(NULL, LOG_WARNING, "WURFL: Error allocating memory for information tree element.\n"); return ERR_WARN; } @@ -338,12 +336,13 @@ static int ha_wurfl_init(void) if (!ebst_insert(&global_wurfl.btree, &wn->nd)) { ha_warning("WURFL: [%s] not inserted in btree\n",wn->name); - send_log(NULL, LOG_WARNING, "WURFL: [%s] not inserted in btree\n",wn->name); return ERR_WARN; } } else { - ha_wurfl_log("WURFL: [%s] already loaded\n",wi->data.name); +#ifdef WURFL_DEBUG + ha_notice("WURFL: [%s] already loaded\n",wi->data.name); +#endif } } @@ -357,10 +356,9 @@ static int ha_wurfl_init(void) list_for_each_entry(wp, &global_wurfl.patch_file_list, list) { if (wurfl_add_patch(global_wurfl.handle, wp->patch_file_path) != WURFL_OK) { ha_warning("WURFL: Engine adding patch file failed - %s\n", wurfl_get_error_message(global_wurfl.handle)); - send_log(NULL, LOG_WARNING, "WURFL: Adding engine patch file failed - %s\n", wurfl_get_error_message(global_wurfl.handle)); return ERR_WARN; } - send_log(NULL, LOG_NOTICE, "WURFL: Engine patch file added %s\n", wp->patch_file_path); + ha_notice("WURFL: Engine patch file added %s\n", wp->patch_file_path); } @@ -381,22 +379,20 @@ static int ha_wurfl_init(void) if (wurfl_result_code != WURFL_OK) { ha_warning("WURFL: Setting cache to [%s] failed - %s\n", global_wurfl.cache_size, wurfl_get_error_message(global_wurfl.handle)); - send_log(NULL, LOG_WARNING, "WURFL: Setting cache to [%s] failed - %s\n", global_wurfl.cache_size, wurfl_get_error_message(global_wurfl.handle)); return ERR_WARN; } - send_log(NULL, LOG_NOTICE, "WURFL: Cache set to [%s]\n", global_wurfl.cache_size); + ha_notice("WURFL: Cache set to [%s]\n", global_wurfl.cache_size); } // loading WURFL engine if (wurfl_load(global_wurfl.handle) != WURFL_OK) { ha_warning("WURFL: Engine load failed - %s\n", wurfl_get_error_message(global_wurfl.handle)); - send_log(NULL, LOG_WARNING, "WURFL: Engine load failed - %s\n", wurfl_get_error_message(global_wurfl.handle)); return ERR_WARN; } - send_log(NULL, LOG_NOTICE, "WURFL: Engine loaded\n"); - send_log(NULL, LOG_NOTICE, "WURFL: Module load completed\n"); + ha_notice("WURFL: Engine loaded\n"); + ha_notice("WURFL: Module load completed\n"); return 0; } -- 2.17.1
From 3d937ba8528c7c8a02e506a0ee4463f369452cad Mon Sep 17 00:00:00 2001 From: mbellomi <[email protected]> Date: Tue, 21 May 2019 15:32:48 +0200 Subject: [PATCH 1/8] BUG/MEDIUM WURFL fixed segfault when a wurfl information not present in wurfl-information-list is referenced in wurfl-get() method --- src/wurfl.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wurfl.c b/src/wurfl.c index 325cba64..7167a929 100644 --- a/src/wurfl.c +++ b/src/wurfl.c @@ -514,9 +514,10 @@ static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char * while (args[i].data.str.area) { chunk_appendf(temp, "%c", global_wurfl.information_list_separator); node = ebst_lookup(&global_wurfl.btree, args[i].data.str.area); - wn = container_of(node, wurfl_data_t, nd); - if (wn) { + if (node) { + + wn = container_of(node, wurfl_data_t, nd); switch(wn->type) { case HA_WURFL_DATA_TYPE_UNKNOWN : -- 2.17.1
From 5d06dbc78945a072fceef4c9bd52898f5b0b8003 Mon Sep 17 00:00:00 2001 From: mbellomi <[email protected]> Date: Tue, 21 May 2019 15:44:53 +0200 Subject: [PATCH 2/8] MINOR WURFL calls header_retrieve_callback from within wurfl dummy library. With this patch, module's code coverage is extended to ha_wurfl_retrieve_header() too --- contrib/wurfl/dummy-wurfl.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contrib/wurfl/dummy-wurfl.c b/contrib/wurfl/dummy-wurfl.c index c87d9b95..8c7909d1 100644 --- a/contrib/wurfl/dummy-wurfl.c +++ b/contrib/wurfl/dummy-wurfl.c @@ -67,6 +67,11 @@ wurfl_error wurfl_load(wurfl_handle hwurfl) wurfl_device_handle wurfl_lookup(wurfl_handle hwurfl, wurfl_header_retrieve_callback header_retrieve_callback, const void *header_retrieve_callback_data) { + // call callback, on a probably existing header + const char *hvalue = header_retrieve_callback("User-Agent", header_retrieve_callback_data); + // and on a non existing one + hvalue = header_retrieve_callback("Non-Existing-Header", header_retrieve_callback_data); + return (void *) 0xdeffa; } -- 2.17.1
From 4585f2a1823d900a4e94877062f347c22c84f138 Mon Sep 17 00:00:00 2001 From: mbellomi <[email protected]> Date: Tue, 21 May 2019 16:29:22 +0200 Subject: [PATCH 5/8] MINOR WURFL removes heading wurfl-information-separator from wurfl-get-all() and wurfl-get() results --- src/wurfl.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/wurfl.c b/src/wurfl.c index 0d03efa6..dc159704 100644 --- a/src/wurfl.c +++ b/src/wurfl.c @@ -442,7 +442,6 @@ static int ha_wurfl_get_all(const struct arg *args, struct sample *smp, const ch chunk_reset(temp); list_for_each_entry(wi, &global_wurfl.information_list, list) { - chunk_appendf(temp, "%c", global_wurfl.information_list_separator); switch(wi->data.type) { case HA_WURFL_DATA_TYPE_UNKNOWN : @@ -478,11 +477,20 @@ static int ha_wurfl_get_all(const struct arg *args, struct sample *smp, const ch break; } + // append wurfl-information-list-separator + chunk_appendf(temp, "%c", global_wurfl.information_list_separator); } wurfl_device_destroy(dHandle); smp->data.u.str.area = temp->area; smp->data.u.str.data = temp->data; + + // remove trailing wurfl-information-list-separator + if (temp->data) { + temp->area[temp->data] = '\0'; + --smp->data.u.str.data; + } + return 1; } @@ -508,7 +516,6 @@ static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char * chunk_reset(temp); while (args[i].data.str.area) { - chunk_appendf(temp, "%c", global_wurfl.information_list_separator); node = ebst_lookup(&global_wurfl.btree, args[i].data.str.area); if (node) { @@ -549,6 +556,9 @@ static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char * break; } + // append wurfl-information-list-separator + chunk_appendf(temp, "%c", global_wurfl.information_list_separator); + } else { ha_wurfl_log("WURFL: %s not in wurfl-information-list \n", args[i].data.str.area); @@ -560,6 +570,13 @@ static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char * wurfl_device_destroy(dHandle); smp->data.u.str.area = temp->area; smp->data.u.str.data = temp->data; + + // remove trailing wurfl-information-list-separator + if (temp->data) { + temp->area[temp->data] = '\0'; + --smp->data.u.str.data; + } + return 1; } -- 2.17.1
From a50b9f4cdb1949fe482f904c63d0dc86de128934 Mon Sep 17 00:00:00 2001 From: mbellomi <[email protected]> Date: Tue, 21 May 2019 16:41:24 +0200 Subject: [PATCH 6/8] MINOR WURFL wurfl_get() and wurfl_get_all() now return an empty string if device detection fails --- src/wurfl.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/wurfl.c b/src/wurfl.c index dc159704..ae3d2fc0 100644 --- a/src/wurfl.c +++ b/src/wurfl.c @@ -433,14 +433,14 @@ static int ha_wurfl_get_all(const struct arg *args, struct sample *smp, const ch wh.wsmp = smp; dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh); + temp = get_trash_chunk(); + chunk_reset(temp); + if (!dHandle) { ha_wurfl_log("WURFL: unable to retrieve device from request %s\n", wurfl_get_error_message(global_wurfl.handle)); - return 1; + goto wurfl_get_all_completed; } - temp = get_trash_chunk(); - chunk_reset(temp); - list_for_each_entry(wi, &global_wurfl.information_list, list) { switch(wi->data.type) { @@ -481,6 +481,8 @@ static int ha_wurfl_get_all(const struct arg *args, struct sample *smp, const ch chunk_appendf(temp, "%c", global_wurfl.information_list_separator); } +wurfl_get_all_completed: + wurfl_device_destroy(dHandle); smp->data.u.str.area = temp->area; smp->data.u.str.data = temp->data; @@ -507,14 +509,14 @@ static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char * wh.wsmp = smp; dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh); + temp = get_trash_chunk(); + chunk_reset(temp); + if (!dHandle) { ha_wurfl_log("WURFL: unable to retrieve device from request %s\n", wurfl_get_error_message(global_wurfl.handle)); - return 1; + goto wurfl_get_completed; } - temp = get_trash_chunk(); - chunk_reset(temp); - while (args[i].data.str.area) { node = ebst_lookup(&global_wurfl.btree, args[i].data.str.area); @@ -567,10 +569,12 @@ static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char * i++; } +wurfl_get_completed: + wurfl_device_destroy(dHandle); smp->data.u.str.area = temp->area; smp->data.u.str.data = temp->data; - + // remove trailing wurfl-information-list-separator if (temp->data) { temp->area[temp->data] = '\0'; -- 2.17.1
From ddf796b3d62fce7cd66ee7d3398ac33602fad6cf Mon Sep 17 00:00:00 2001 From: mbellomi <[email protected]> Date: Tue, 21 May 2019 17:15:59 +0200 Subject: [PATCH 8/8] MINOR WURFL module version bump --- src/wurfl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wurfl.c b/src/wurfl.c index d1876014..5d720af6 100644 --- a/src/wurfl.c +++ b/src/wurfl.c @@ -70,7 +70,7 @@ typedef struct { struct ebmb_node nd; } wurfl_data_t; -static const char HA_WURFL_MODULE_VERSION[] = "1.0"; +static const char HA_WURFL_MODULE_VERSION[] = "2.0"; static const char HA_WURFL_ISDEVROOT_FALSE[] = "FALSE"; static const char HA_WURFL_ISDEVROOT_TRUE[] = "TRUE"; -- 2.17.1
From 27074e6c827605a79dd271b813b162dfbc088e0f Mon Sep 17 00:00:00 2001 From: mbellomi <[email protected]> Date: Tue, 21 May 2019 17:15:07 +0200 Subject: [PATCH 7/8] MEDIUM WURFL HTX awareness. Now wurfl fetch process is fully HTX aware --- src/wurfl.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/src/wurfl.c b/src/wurfl.c index ae3d2fc0..d1876014 100644 --- a/src/wurfl.c +++ b/src/wurfl.c @@ -10,6 +10,8 @@ #include <proto/arg.h> #include <proto/log.h> #include <proto/proto_http.h> +#include <proto/http_fetch.h> +#include <proto/http_htx.h> #include <proto/sample.h> #include <ebsttree.h> #include <ebmbtree.h> @@ -428,9 +430,27 @@ static int ha_wurfl_get_all(const struct arg *args, struct sample *smp, const ch struct buffer *temp; wurfl_information_t *wi; ha_wurfl_header_t wh; + struct channel *chn; ha_wurfl_log("WURFL: starting ha_wurfl_get_all\n"); + + chn = (smp->strm ? &smp->strm->req : NULL); + + if (smp->px->options2 & PR_O2_USE_HTX) { + /* HTX version */ + struct htx *htx = smp_prefetch_htx(smp, chn, 1); + + if (!htx) { + return 0; + } + + } else { + /* Legacy version */ + CHECK_HTTP_MESSAGE_FIRST(chn); + } + wh.wsmp = smp; + dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh); temp = get_trash_chunk(); @@ -493,6 +513,7 @@ wurfl_get_all_completed: --smp->data.u.str.data; } + smp->data.type = SMP_T_STR; return 1; } @@ -504,9 +525,27 @@ static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char * struct ebmb_node *node; ha_wurfl_header_t wh; int i = 0; + struct channel *chn; ha_wurfl_log("WURFL: starting ha_wurfl_get\n"); + + chn = (smp->strm ? &smp->strm->req : NULL); + + if (smp->px->options2 & PR_O2_USE_HTX) { + /* HTX version */ + struct htx *htx = smp_prefetch_htx(smp, chn, 1); + + if (!htx) { + return 0; + } + + } else { + /* Legacy version */ + CHECK_HTTP_MESSAGE_FIRST(chn); + } + wh.wsmp = smp; + dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh); temp = get_trash_chunk(); @@ -581,6 +620,7 @@ wurfl_get_completed: --smp->data.u.str.data; } + smp->data.type = SMP_T_STR; return 1; } @@ -700,25 +740,80 @@ static const char *(*ha_wurfl_get_property_callback(char *name)) (wurfl_handle w static const char *ha_wurfl_retrieve_header(const char *header_name, const void *wh) { struct sample *smp; - struct hdr_idx *idx; - struct hdr_ctx ctx; - const struct http_msg *msg; + struct channel *chn; int header_len = HA_WURFL_MAX_HEADER_LENGTH; - ha_wurfl_log("WURFL: retrieve header request [%s]\n", header_name); smp = ((ha_wurfl_header_t *)wh)->wsmp; - idx = &smp->strm->txn->hdr_idx; - msg = &smp->strm->txn->req; - ctx.idx = 0; + chn = (smp->strm ? &smp->strm->req : NULL); + + if (smp->px->options2 & PR_O2_USE_HTX) { + /* HTX version */ + struct htx *htx; + struct http_hdr_ctx ctx; + struct ist name; - if (http_find_full_header2(header_name, strlen(header_name), ci_head(msg->chn), idx, &ctx) == 0) - return 0; + ha_wurfl_log("WURFL: retrieve header (HTX) request [%s]\n", header_name); - if (header_len > ctx.vlen) - header_len = ctx.vlen; + //the header is searched from the beginning + ctx.blk = NULL; + + // We could skip this chek since ha_wurfl_retrieve_header is called from inside + // ha_wurfl_get()/ha_wurfl_get_all() that already perform the same check + // We choose to keep it in case ha_wurfl_retrieve_header will be called directly + htx = smp_prefetch_htx(smp, chn, 1); + if (!htx) { + return NULL; + } + + name.ptr = (char *)header_name; + name.len = strlen(header_name); + + // If 4th param is set, it works on full-line headers in whose comma is not a delimiter but is + // part of the syntax + if (!http_find_header(htx, name, &ctx, 1)) { + return NULL; + } + + if (header_len > ctx.value.len) + header_len = ctx.value.len; + + strncpy(((ha_wurfl_header_t *)wh)->header_value, ctx.value.ptr, header_len); + + } else { + /* Legacy version */ + struct http_txn *txn; + struct hdr_idx *idx; + struct hdr_ctx ctx; + int res; + + ha_wurfl_log("WURFL: retrieve header (legacy) request [%s]\n", header_name); + + // We could skip this chek since ha_wurfl_retrieve_header is called from inside + // ha_wurfl_get()/ha_wurfl_get_all() that already perform the same check + // We choose to keep it in case ha_wurfl_retrieve_header will be called directly + // This is a version of CHECK_HTTP_MESSAGE_FIRST(chn) which returns NULL in case of error + res = smp_prefetch_http(smp->px, smp->strm, smp->opt, (chn), smp, 1); + if (res <= 0) { + return NULL; + } + + txn = smp->strm->txn; + idx = &txn->hdr_idx; + + ctx.idx = 0; + + if (http_find_full_header2(header_name, strlen(header_name), ci_head(chn), idx, &ctx) == 0) + return NULL; + + if (header_len > ctx.vlen) + header_len = ctx.vlen; + + strncpy(((ha_wurfl_header_t *)wh)->header_value, ctx.line + ctx.val, header_len); + + } - strncpy(((ha_wurfl_header_t *)wh)->header_value, ctx.line + ctx.val, header_len); ((ha_wurfl_header_t *)wh)->header_value[header_len] = '\0'; + ha_wurfl_log("WURFL: retrieve header request returns [%s]\n", ((ha_wurfl_header_t *)wh)->header_value); return ((ha_wurfl_header_t *)wh)->header_value; } -- 2.17.1

