]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
Revert "CLEANUP: wurfl: remove dead, broken and unmaintained code"
authorWilly Tarreau <w@1wt.eu>
Fri, 19 Apr 2019 14:03:32 +0000 (16:03 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 23 Apr 2019 08:34:43 +0000 (10:34 +0200)
This reverts commit 8e5e1e7bf000f2603a085c76be118214d22c55b4.

The following patches will fix this code and may be backported.

INSTALL
Makefile
doc/WURFL-device-detection.txt [new file with mode: 0644]
doc/configuration.txt
examples/wurfl-example.cfg [new file with mode: 0644]
src/wurfl.c [new file with mode: 0644]

diff --git a/INSTALL b/INSTALL
index b7a8c4c0fa5186142ffc9b61b243315557d408fe..9df17caf68c4f00cdaaaec2ec929f0af66e1d297 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -316,6 +316,7 @@ following files :
 
     doc/DeviceAtlas-device-detection.txt   for DeviceAtlas
     doc/51Degrees-device-detection.txt     for 51Degrees
+    doc/WURFL-device-detection.txt         for Scientiamobile WURFL
 
 
 4.9) Miscellaneous
index 35772be90dc8692fa53a2569ad0c4bff2b6ffa37..e7a9db0761c4bef1ce7c9c84279ff8f4f180db27 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -47,6 +47,7 @@
 #   USE_RT               : enable it if your system requires -lrt. Automatic on Linux.
 #   USE_DEVICEATLAS      : enable DeviceAtlas api.
 #   USE_51DEGREES        : enable third party device detection library from 51Degrees
+#   USE_WURFL            : enable WURFL detection library from Scientiamobile
 #   USE_SYSTEMD          : enable sd_notify() support.
 #   USE_OBSOLETE_LINKER  : use when the linker fails to emit __start_init/__stop_init
 #
@@ -624,6 +625,24 @@ endif
 OPTIONS_LDFLAGS += $(if $(51DEGREES_LIB),-L$(51DEGREES_LIB)) -lm
 endif
 
+ifneq ($(USE_WURFL),)
+# Use WURFL_SRC and possibly WURFL_INC and WURFL_LIB to force path
+# to WURFL headers and libraries if needed.
+WURFL_SRC =
+WURFL_INC = $(WURFL_SRC)
+WURFL_LIB = $(WURFL_SRC)
+OPTIONS_OBJS    += src/wurfl.o
+OPTIONS_CFLAGS  += -DUSE_WURFL $(if $(WURFL_INC),-I$(WURFL_INC))
+ifneq ($(WURFL_DEBUG),)
+OPTIONS_CFLAGS  += -DWURFL_DEBUG
+endif
+ifneq ($(WURFL_HEADER_WITH_DETAILS),)
+OPTIONS_CFLAGS  += -DWURFL_HEADER_WITH_DETAILS
+endif
+BUILD_OPTIONS   += $(call ignore_implicit,USE_WURFL)
+OPTIONS_LDFLAGS += $(if $(WURFL_LIB),-L$(WURFL_LIB)) -lwurfl
+endif
+
 ifneq ($(USE_SYSTEMD),)
 OPTIONS_LDFLAGS += -lsystemd
 endif
diff --git a/doc/WURFL-device-detection.txt b/doc/WURFL-device-detection.txt
new file mode 100644 (file)
index 0000000..c968615
--- /dev/null
@@ -0,0 +1,68 @@
+Scientiamobile WURFL Device Detection
+-------------------------------------
+
+You can also include WURFL for inbuilt device detection enabling attributes.
+
+WURFL is a high-performance and low-memory footprint mobile device detection
+software component that can quickly and accurately detect over 500 capabilities
+of visiting devices. It can differentiate between portable mobile devices, desktop devices,
+SmartTVs and any other types of devices on which a web browser can be installed.
+
+In order to add WURFL device detection support, you would need to download Scientiamobile
+InFuze C API and install it on your system. Refer to www.scientiamobile.com to obtain a valid
+InFuze license.
+Compile haproxy as shown :
+
+    $ make TARGET=<target> USE_WURFL=1
+
+Optionally WURFL_DEBUG=1 may be set to increase logs verbosity
+
+These are the supported WURFL directives (see doc/configuration.txt) :
+- wurfl-data-file <path to WURFL data file>
+- wurfl-information-list [<string>] (list of WURFL capabilities,
+   virtual capabilities, property names we plan to use in injected headers)
+- wurfl-information-list-separator <char> (character that will be
+   used to separate values in a response header, ',' by default).
+- wurfl-engine-mode <string> (Sets the WURFL engine target. You can choose
+   between "accuracy" and "performance","performance" by default)
+- wurfl-cache-size <string> (Sets the WURFL caching strategy)
+- wurfl-patch-file [<file path>] (Sets the paths to custom WURFL patch files)
+
+Sample configuration :
+
+    global
+       wurfl-data-file /usr/share/wurfl/wurfl-eval.xml
+
+       wurfl-information-list wurfl_id model_name
+
+       #wurfl-information-list-separator |
+
+       wurfl-engine-mode performance
+       #wurfl-engine-mode accuracy
+
+       ## double LRU cache
+       wurfl-cache-size 100000,30000
+       ## single LRU cache
+       #wurfl-cache-size 100000
+       ## no cache
+       #wurfl-cache-size 0
+
+       #wurfl-patch-file <paths to custom patch files>
+
+    ...
+    frontend
+       bind *:8888
+       default_backend servers
+
+There are two distinct methods available to transmit the WURFL data downstream
+to the target application:
+
+All data listed in wurfl-information-list
+
+    http-request set-header X-WURFL-All %[wurfl-get-all()]
+
+A subset of data listed in wurfl-information-list
+
+    http-request set-header X-WURFL-Properties %[wurfl-get(wurfl_id,is_tablet)]
+
+Please find more information about WURFL and the detection methods at https://www.scientiamobile.com
index 47edf38468a813d94d52bd841e316f4ab29bcf8b..bae19799976efde4691cc8de075d54d799eb6b84 100644 (file)
@@ -597,6 +597,12 @@ The following keywords are supported in the "global" section :
    - 51degrees-property-name-list
    - 51degrees-property-separator
    - 51degrees-cache-size
+   - wurfl-data-file
+   - wurfl-information-list
+   - wurfl-information-list-separator
+   - wurfl-engine-mode
+   - wurfl-cache-size
+   - wurfl-useragent-priority
 
  * Performance tuning
    - max-spread-checks
@@ -1231,6 +1237,95 @@ description <text>
   Please note that this option is only available when haproxy has been
   compiled with USE_51DEGREES.
 
+wurfl-data-file <file path>
+  The path of the WURFL data file to provide device detection services. The
+  file should be accessible by HAProxy with relevant permissions.
+
+  Please note that this option is only available when haproxy has been compiled
+  with USE_WURFL=1.
+
+wurfl-information-list [<capability>]*
+  A space-delimited list of WURFL capabilities, virtual capabilities, property
+  names we plan to use in injected headers. A full list of capability and
+  virtual capability names is available on the Scientiamobile website :
+
+      https://www.scientiamobile.com/wurflCapability
+
+  Valid WURFL properties are:
+  - wurfl_id                    Contains the device ID of the matched device.
+
+  - wurfl_root_id               Contains the device root ID of the matched
+                                device.
+
+  - wurfl_isdevroot             Tells if the matched device is a root device.
+                                Possible values are "TRUE" or "FALSE".
+
+  - wurfl_useragent             The original useragent coming with this
+                                particular web request.
+
+  - wurfl_api_version           Contains a string representing the currently
+                                used Libwurfl API version.
+
+  - wurfl_engine_target         Contains a string representing the currently
+                                set WURFL Engine Target. Possible values are
+                                "HIGH_ACCURACY", "HIGH_PERFORMANCE", "INVALID".
+
+  - wurfl_info                  A string containing information on the parsed
+                                wurfl.xml and its full path.
+
+  - wurfl_last_load_time        Contains the UNIX timestamp of the last time
+                                WURFL has been loaded successfully.
+
+  - wurfl_normalized_useragent  The normalized useragent.
+
+  - wurfl_useragent_priority    The user agent priority used by WURFL.
+
+  Please note that this option is only available when haproxy has been compiled
+  with USE_WURFL=1.
+
+wurfl-information-list-separator <char>
+  A char that will be used to separate values in a response header containing
+  WURFL results. If not set that a comma (',') will be used by default.
+
+  Please note that this option is only available when haproxy has been compiled
+  with USE_WURFL=1.
+
+wurfl-patch-file [<file path>]
+  A list of WURFL patch file paths. Note that patches are loaded during startup
+  thus before the chroot.
+
+  Please note that this option is only available when haproxy has been compiled
+  with USE_WURFL=1.
+
+wurfl-engine-mode { accuracy | performance }
+  Sets the WURFL engine target. You can choose between 'accuracy' or
+  'performance' targets. In performance mode, desktop web browser detection is
+  done programmatically without referencing the WURFL data. As a result, most
+  desktop web browsers are returned as generic_web_browser WURFL ID for
+  performance. If either performance or accuracy are not defined, performance
+  mode is enabled by default.
+
+  Please note that this option is only available when haproxy has been compiled
+  with USE_WURFL=1.
+
+wurfl-cache-size <U>[,<D>]
+  Sets the WURFL caching strategy. Here <U> is the Useragent cache size, and
+  <D> is the internal device cache size. There are three possibilities here :
+  - "0"     : no cache is used.
+  - <U>     : the Single LRU cache is used, the size is expressed in elements.
+  - <U>,<D> : the Double LRU cache is used, both sizes are in elements. This is
+              the highest performing option.
+
+  Please note that this option is only available when haproxy has been compiled
+  with USE_WURFL=1.
+
+wurfl-useragent-priority { plain | sideloaded_browser }
+  Tells WURFL if it should prioritize use of the plain user agent ('plain')
+  over the default sideloaded browser user agent ('sideloaded_browser').
+
+  Please note that this option is only available when haproxy has been compiled
+  with USE_WURFL=1.
+
 
 3.2. Performance tuning
 -----------------------
diff --git a/examples/wurfl-example.cfg b/examples/wurfl-example.cfg
new file mode 100644 (file)
index 0000000..ad651a8
--- /dev/null
@@ -0,0 +1,49 @@
+#
+# This is an example of how to configure HAProxy to be used with WURFL Device Detection module.
+#
+# HAProxy needs to be compiled with support for this. See README section 1.3
+#
+
+global
+
+       # The WURFL data file
+       wurfl-data-file         /usr/share/wurfl/wurfl-eval.xml
+
+       # WURFL patches definition (as much as needed, patches will be applied in the same order as specified in this conf file)
+       #wurfl-patch-file       /path/to/patch1.xml;
+
+       # WURFL engine target: one of the following (default is performance)
+       wurfl-engine-mode       performance
+       #wurfl-engine-mode      accuracy
+
+       # WURFL cache: one of the following
+       ## double LRU cache
+       wurfl-cache-size        100000,30000
+       ## single LRU cache
+       #wurfl-cache-size       100000
+       ## no cache
+       #wurfl-cache-size       0
+
+       wurfl-information-list-separator |
+
+       # list of WURFL capabilities, virtual capabilities, property names planned to be used in injected headers
+       wurfl-information-list wurfl_id model_name
+
+defaults
+       mode http
+       timeout connect         30s
+       timeout client          30s
+       timeout server          30s
+
+frontend TheFrontend
+       bind                    192.168.1.22:80
+       default_backend         TheBackend
+
+       # inject a header called X-Wurfl-All with all the WURFL informations listed in wurfl-information-list
+       http-request set-header X-Wurfl-All %[wurfl-get-all()]
+
+       # inject a header called X-WURFL-PROPERTIES with the "wurfl_id" information (should be listed in wurfl-information-list)
+       #http-request set-header X-WURFL-PROPERTIES %[wurfl-get(wurfl_id)]
+
+backend TheBackend
+       server                  TheWebServer 192.168.0.40:80
diff --git a/src/wurfl.c b/src/wurfl.c
new file mode 100644 (file)
index 0000000..75dcf00
--- /dev/null
@@ -0,0 +1,800 @@
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <common/cfgparse.h>
+#include <common/chunk.h>
+#include <common/buffer.h>
+#include <common/errors.h>
+#include <common/initcall.h>
+#include <types/global.h>
+#include <proto/arg.h>
+#include <proto/log.h>
+#include <proto/proto_http.h>
+#include <proto/sample.h>
+#include <ebsttree.h>
+#include <ebmbtree.h>
+
+#include <wurfl/wurfl.h>
+
+static struct {
+       char *data_file; /* the WURFL data file */
+       char *cache_size; /* the WURFL cache parameters */
+       int engine_mode; /* the WURFL engine mode */
+       int useragent_priority; /* the WURFL ua priority */
+       struct list patch_file_list; /* the list of WURFL patch file to use */
+       char information_list_separator; /* the separator used in request to separate values */
+       struct list information_list; /* the list of WURFL data to return into request */
+       void *handle; /* the handle to WURFL engine */
+       struct eb_root btree; /* btree containing info (name/type) on WURFL data to return */
+} global_wurfl = {
+       .data_file = NULL,
+       .cache_size = NULL,
+       .engine_mode = -1,
+       .useragent_priority = -1,
+       .information_list_separator = ',',
+       .information_list = LIST_HEAD_INIT(global_wurfl.information_list),
+       .patch_file_list = LIST_HEAD_INIT(global_wurfl.patch_file_list),
+       .handle = NULL,
+};
+
+#ifdef WURFL_DEBUG
+inline static void ha_wurfl_log(char * message, ...)
+{
+       char logbuf[256];
+       va_list argp;
+
+       va_start(argp, message);
+       vsnprintf(logbuf, sizeof(logbuf), message, argp);
+       va_end(argp);
+       send_log(NULL, LOG_NOTICE, logbuf, NULL);
+}
+#else
+inline static void ha_wurfl_log(char * message, ...)
+{
+}
+#endif
+
+#define HA_WURFL_MAX_HEADER_LENGTH 1024
+
+typedef char *(*PROP_CALLBACK_FUNC)(wurfl_handle wHandle, wurfl_device_handle dHandle);
+
+enum wurfl_data_type {
+       HA_WURFL_DATA_TYPE_UNKNOWN = 0,
+       HA_WURFL_DATA_TYPE_CAP = 100,
+       HA_WURFL_DATA_TYPE_VCAP = 200,
+       HA_WURFL_DATA_TYPE_PROPERTY = 300
+};
+
+typedef struct {
+       char *name;
+       enum wurfl_data_type type;
+       PROP_CALLBACK_FUNC func_callback;
+       struct ebmb_node nd;
+} wurfl_data_t;
+
+static const char HA_WURFL_MODULE_VERSION[] = "1.0";
+static const char HA_WURFL_ISDEVROOT_FALSE[] = "FALSE";
+static const char HA_WURFL_ISDEVROOT_TRUE[] = "TRUE";
+static const char HA_WURFL_TARGET_ACCURACY[] = "accuracy";
+static const char HA_WURFL_TARGET_PERFORMANCE[] = "performance";
+static const char HA_WURFL_PRIORITY_PLAIN[] = "plain";
+static const char HA_WURFL_PRIORITY_SIDELOADED_BROWSER[] = "sideloaded_browser";
+static const char HA_WURFL_MIN_ENGINE_VERSION_MANDATORY[] = "1.8.0.0";
+
+static const char HA_WURFL_DATA_TYPE_UNKNOWN_STRING[] = "unknown";
+static const char HA_WURFL_DATA_TYPE_CAP_STRING[] = "capability";
+static const char HA_WURFL_DATA_TYPE_VCAP_STRING[] = "virtual_capability";
+static const char HA_WURFL_DATA_TYPE_PROPERTY_STRING[] = "property";
+
+static const char *ha_wurfl_retrieve_header(const char *header_name, const void *wh);
+static const char *ha_wurfl_get_wurfl_root_id (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_id (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_isdevroot (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_api_version (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_engine_target (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_info (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_last_load_time (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_normalized_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_useragent_priority (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *(*ha_wurfl_get_property_callback(char *name)) (wurfl_handle wHandle, wurfl_device_handle dHandle);
+
+// ordered property=>function map, suitable for binary search
+static const struct {
+       const char *name;
+       const char *(*func)(wurfl_handle wHandle, wurfl_device_handle dHandle);
+} wurfl_properties_function_map [] = {
+       {"wurfl_api_version", ha_wurfl_get_wurfl_api_version},
+       {"wurfl_engine_target", ha_wurfl_get_wurfl_engine_target},
+       {"wurfl_id", ha_wurfl_get_wurfl_id },
+       {"wurfl_info", ha_wurfl_get_wurfl_info },
+       {"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_useragent", ha_wurfl_get_wurfl_useragent},
+       {"wurfl_useragent_priority", ha_wurfl_get_wurfl_useragent_priority },
+       {"wurfl_root_id", ha_wurfl_get_wurfl_root_id},
+};
+static const int HA_WURFL_PROPERTIES_NBR = 10;
+
+typedef struct {
+       struct list list;
+       wurfl_data_t data;
+} wurfl_information_t;
+
+typedef struct {
+       struct list list;
+       char *patch_file_path;
+} wurfl_patches_t;
+
+typedef struct {
+       struct sample *wsmp;
+       char header_value[HA_WURFL_MAX_HEADER_LENGTH + 1];
+} ha_wurfl_header_t;
+
+/*
+ * configuration parameters parsing functions
+ */
+static int ha_wurfl_cfg_data_file(char **args, int section_type, struct proxy *curpx,
+                                  struct proxy *defpx, const char *file, int line,
+                                  char **err)
+{
+
+       if (*(args[1]) == 0) {
+               memprintf(err, "WURFL: %s expects a value.\n", args[0]);
+               return -1;
+       }
+
+       global_wurfl.data_file = strdup(args[1]);
+       return 0;
+}
+
+static int ha_wurfl_cfg_cache(char **args, int section_type, struct proxy *curpx,
+                              struct proxy *defpx, const char *file, int line,
+                              char **err)
+{
+       if (*(args[1]) == 0) {
+               memprintf(err, "WURFL: %s expects a value.\n", args[0]);
+               return -1;
+       }
+
+       global_wurfl.cache_size = strdup(args[1]);
+       return 0;
+}
+
+static int ha_wurfl_cfg_engine_mode(char **args, int section_type, struct proxy *curpx,
+                                    struct proxy *defpx, const char *file, int line,
+                                    char **err)
+{
+       if (*(args[1]) == 0) {
+               memprintf(err, "WURFL: %s expects a value.\n", args[0]);
+               return -1;
+       }
+
+       if (!strcmp(args[1],HA_WURFL_TARGET_ACCURACY)) {
+               global_wurfl.engine_mode = WURFL_ENGINE_TARGET_HIGH_ACCURACY;
+               return 0;
+       }
+
+       if (!strcmp(args[1],HA_WURFL_TARGET_PERFORMANCE)) {
+               global_wurfl.engine_mode = WURFL_ENGINE_TARGET_HIGH_PERFORMANCE;
+               return 0;
+       }
+
+       memprintf(err, "WURFL: %s valid values are %s or %s.\n", args[0], HA_WURFL_TARGET_PERFORMANCE, HA_WURFL_TARGET_ACCURACY);
+       return -1;
+}
+
+static int ha_wurfl_cfg_information_list_separator(char **args, int section_type, struct proxy *curpx,
+                                                   struct proxy *defpx, const char *file, int line,
+                                                   char **err)
+{
+       if (*(args[1]) == 0) {
+               memprintf(err, "WURFL: %s expects a single character.\n", args[0]);
+               return -1;
+       }
+
+       if (strlen(args[1]) > 1) {
+               memprintf(err, "WURFL: %s expects a single character, got %s.\n", args[0], args[1]);
+               return -1;
+       }
+
+       global_wurfl.information_list_separator = *args[1];
+       return 0;
+}
+
+static int ha_wurfl_cfg_information_list(char **args, int section_type, struct proxy *curpx,
+                                         struct proxy *defpx, const char *file, int line,
+                                         char **err)
+{
+       int argIdx = 1;
+       wurfl_information_t *wi;
+
+       if (*(args[argIdx]) == 0) {
+               memprintf(err, "WURFL: %s expects a value.\n", args[0]);
+               return -1;
+       }
+
+       while (*(args[argIdx])) {
+               wi = calloc(1, sizeof(*wi));
+
+               if (wi == NULL) {
+                       memprintf(err, "WURFL: Error allocating memory for %s element.\n", args[0]);
+                       return -1;
+               }
+
+               wi->data.name = strdup(args[argIdx]);
+               wi->data.type = HA_WURFL_DATA_TYPE_UNKNOWN;
+               wi->data.func_callback = NULL;
+               LIST_ADDQ(&global_wurfl.information_list, &wi->list);
+               ++argIdx;
+       }
+
+       return 0;
+}
+
+static int ha_wurfl_cfg_patch_file_list(char **args, int section_type, struct proxy *curpx,
+                                        struct proxy *defpx, const char *file, int line,
+                                        char **err)
+{
+       int argIdx = 1;
+       wurfl_patches_t *wp;
+
+       if (*(args[argIdx]) == 0) {
+               memprintf(err, "WURFL: %s expects a value.\n", args[0]);
+               return -1;
+       }
+
+       while (*(args[argIdx])) {
+               wp = calloc(1, sizeof(*wp));
+
+               if (wp == NULL) {
+                       memprintf(err, "WURFL: Error allocating memory for %s element.\n", args[0]);
+                       return -1;
+               }
+
+               wp->patch_file_path = strdup(args[argIdx]);
+               LIST_ADDQ(&global_wurfl.patch_file_list, &wp->list);
+               ++argIdx;
+       }
+
+       return 0;
+}
+
+static int ha_wurfl_cfg_useragent_priority(char **args, int section_type, struct proxy *curpx,
+                                           struct proxy *defpx, const char *file, int line,
+                                           char **err)
+{
+       if (*(args[1]) == 0) {
+               memprintf(err, "WURFL: %s expects a value.\n", args[0]);
+               return -1;
+       }
+
+       if (!strcmp(args[1],HA_WURFL_PRIORITY_PLAIN)) {
+               global_wurfl.useragent_priority = WURFL_USERAGENT_PRIORITY_USE_PLAIN_USERAGENT;
+               return 0;
+       }
+
+       if (!strcmp(args[1],HA_WURFL_PRIORITY_SIDELOADED_BROWSER)) {
+               global_wurfl.useragent_priority = WURFL_USERAGENT_PRIORITY_OVERRIDE_SIDELOADED_BROWSER_USERAGENT;
+               return 0;
+       }
+
+       memprintf(err, "WURFL: %s valid values are %s or %s.\n", args[0], HA_WURFL_PRIORITY_PLAIN, HA_WURFL_PRIORITY_SIDELOADED_BROWSER);
+       return -1;
+}
+
+/*
+ * module init / deinit functions. Returns 0 if OK, or a combination of ERR_*.
+ */
+
+static int ha_wurfl_init(void)
+{
+       wurfl_information_t *wi;
+       wurfl_patches_t *wp;
+       wurfl_data_t * wn;
+       int wurfl_result_code = WURFL_OK;
+       int len;
+
+       send_log(NULL, LOG_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");
+               return ERR_WARN;
+       }
+
+       send_log(NULL, LOG_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 (global.nbthread > 1) {
+               ha_alert("WURFL: multithreading is not supported for now.\n");
+               return (ERR_FATAL | ERR_ALERT);
+       }
+
+       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);
+       // 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);
+
+       // 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
+               global_wurfl.btree = EB_ROOT;
+
+               // checking if informations are valid WURFL data ( cap, vcaps, properties )
+               list_for_each_entry(wi, &global_wurfl.information_list, list) {
+                       // check if information is already loaded looking into btree
+                       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);
+                               } 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);
+                               } 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);
+                               }
+
+                               // ebtree insert here
+                               len = strlen(wi->data.name);
+
+                               wn = malloc(sizeof(wurfl_data_t) + len + 1);
+
+                               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;
+                               }
+
+                               wn->name = wi->data.name;
+                               wn->type = wi->data.type;
+                               wn->func_callback = wi->data.func_callback;
+                               memcpy(wn->nd.key, wi->data.name, len);
+                               wn->nd.key[len] = 0;
+
+                               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);
+                       }
+
+               }
+
+       }
+
+       // filtering mandatory capabilities if engine version < 1.8.0.0
+       if (strcmp(wurfl_get_api_version(), HA_WURFL_MIN_ENGINE_VERSION_MANDATORY) < 0) {
+               wurfl_capability_enumerator_handle hmandatorycapabilityenumerator;
+               ha_wurfl_log("WURFL: Engine version %s < %s - Filtering mandatory capabilities\n", wurfl_get_api_version(), HA_WURFL_MIN_ENGINE_VERSION_MANDATORY);
+               hmandatorycapabilityenumerator = wurfl_get_mandatory_capability_enumerator(global_wurfl.handle);
+
+               while (wurfl_capability_enumerator_is_valid(hmandatorycapabilityenumerator)) {
+                       char *name = (char *)wurfl_capability_enumerator_get_name(hmandatorycapabilityenumerator);
+
+                       if (ebst_lookup(&global_wurfl.btree, name) == NULL) {
+                               if (wurfl_add_requested_capability(global_wurfl.handle, name) != WURFL_OK) {
+                                       ha_warning("WURFL: Engine adding mandatory capability [%s] failed - %s\n", name, wurfl_get_error_message(global_wurfl.handle));
+                                       send_log(NULL, LOG_WARNING, "WURFL: Adding mandatory capability [%s] failed - %s\n", name, wurfl_get_error_message(global_wurfl.handle));
+                                       return ERR_WARN;
+                               }
+
+                               ha_wurfl_log("WURFL: Mandatory capability [%s] added\n", name);
+                       } else {
+                               ha_wurfl_log("WURFL: Mandatory capability [%s] already filtered\n", name);
+                       }
+
+                       wurfl_capability_enumerator_move_next(hmandatorycapabilityenumerator);
+               }
+
+               wurfl_capability_enumerator_destroy(hmandatorycapabilityenumerator);
+       }
+
+       // adding WURFL patches if needed
+       if (!LIST_ISEMPTY(&global_wurfl.patch_file_list)) {
+
+               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);
+
+               }
+
+       }
+
+       // setting cache provider if specified in cfg, otherwise let engine choose
+       if (global_wurfl.cache_size != NULL) {
+               if (strpbrk(global_wurfl.cache_size, ",") != NULL) {
+                       wurfl_result_code = wurfl_set_cache_provider(global_wurfl.handle, WURFL_CACHE_PROVIDER_DOUBLE_LRU, global_wurfl.cache_size) ;
+               } else {
+                       if (strcmp(global_wurfl.cache_size, "0")) {
+                               wurfl_result_code = wurfl_set_cache_provider(global_wurfl.handle, WURFL_CACHE_PROVIDER_LRU, global_wurfl.cache_size) ;
+                       } else {
+                               wurfl_result_code = wurfl_set_cache_provider(global_wurfl.handle, WURFL_CACHE_PROVIDER_NONE, 0);
+                       }
+
+               }
+
+               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);
+       }
+
+       // setting engine mode if specified in cfg, otherwise let engine choose
+       if (global_wurfl.engine_mode != -1) {
+               if (wurfl_set_engine_target(global_wurfl.handle, global_wurfl.engine_mode) != WURFL_OK ) {
+                       ha_warning("WURFL: Setting engine target failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
+                       send_log(NULL, LOG_WARNING, "WURFL: Setting engine target failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
+                       return ERR_WARN;
+               }
+
+       }
+
+       send_log(NULL, LOG_NOTICE, "WURFL: Engine target set to [%s]\n", (global_wurfl.engine_mode == WURFL_ENGINE_TARGET_HIGH_PERFORMANCE) ? (HA_WURFL_TARGET_PERFORMANCE) : (HA_WURFL_TARGET_ACCURACY) );
+
+       // setting ua priority if specified in cfg, otherwise let engine choose
+       if (global_wurfl.useragent_priority != -1) {
+               if (wurfl_set_useragent_priority(global_wurfl.handle, global_wurfl.useragent_priority) != WURFL_OK ) {
+                       ha_warning("WURFL: Setting engine useragent priority failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
+                       send_log(NULL, LOG_WARNING, "WURFL: Setting engine useragent priority failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
+                       return ERR_WARN;
+               }
+
+       }
+
+       send_log(NULL, LOG_NOTICE, "WURFL: Engine useragent priority set to [%s]\n", (global_wurfl.useragent_priority == WURFL_USERAGENT_PRIORITY_USE_PLAIN_USERAGENT) ? (HA_WURFL_PRIORITY_PLAIN) : (HA_WURFL_PRIORITY_SIDELOADED_BROWSER) );
+
+       // 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");
+       return 0;
+}
+
+static void ha_wurfl_deinit(void)
+{
+       wurfl_information_t *wi, *wi2;
+       wurfl_patches_t *wp, *wp2;
+
+       send_log(NULL, LOG_NOTICE, "WURFL: Unloading module v.%s\n", HA_WURFL_MODULE_VERSION);
+       wurfl_destroy(global_wurfl.handle);
+       global_wurfl.handle = NULL;
+       free(global_wurfl.data_file);
+       global_wurfl.data_file = NULL;
+       free(global_wurfl.cache_size);
+       global_wurfl.cache_size = NULL;
+
+       list_for_each_entry_safe(wi, wi2, &global_wurfl.information_list, list) {
+               LIST_DEL(&wi->list);
+               free(wi);
+       }
+
+       list_for_each_entry_safe(wp, wp2, &global_wurfl.patch_file_list, list) {
+               LIST_DEL(&wp->list);
+               free(wp);
+       }
+
+       send_log(NULL, LOG_NOTICE, "WURFL: Module unloaded\n");
+}
+
+static int ha_wurfl_get_all(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+       wurfl_device_handle dHandle;
+       struct buffer *temp;
+       wurfl_information_t *wi;
+       ha_wurfl_header_t wh;
+
+       ha_wurfl_log("WURFL: starting ha_wurfl_get_all\n");
+       wh.wsmp = smp;
+       dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh);
+
+       if (!dHandle) {
+               ha_wurfl_log("WURFL: unable to retrieve device from request %s\n", wurfl_get_error_message(global_wurfl.handle));
+               return 1;
+       }
+
+       temp = get_trash_chunk();
+       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 :
+                       ha_wurfl_log("WURFL: %s is of an %s type\n", wi->data.name, HA_WURFL_DATA_TYPE_UNKNOWN_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+                       // write WURFL property type and name before its value...
+                       chunk_appendf(temp, "%s=%s", HA_WURFL_DATA_TYPE_UNKNOWN_STRING, wi->data.name);
+#endif
+                       break;
+               case HA_WURFL_DATA_TYPE_CAP :
+                       ha_wurfl_log("WURFL: %s is a %s\n", wi->data.name, HA_WURFL_DATA_TYPE_CAP_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+                       // write WURFL property type and name before its value...
+                       chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_CAP_STRING, wi->data.name);
+#endif
+                       chunk_appendf(temp, "%s", wurfl_device_get_capability(dHandle, wi->data.name));
+                       break;
+               case HA_WURFL_DATA_TYPE_VCAP :
+                       ha_wurfl_log("WURFL: %s is a %s\n", wi->data.name, HA_WURFL_DATA_TYPE_VCAP_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+                       // write WURFL property type and name before its value...
+                       chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_VCAP_STRING, wi->data.name);
+#endif
+                       chunk_appendf(temp, "%s", wurfl_device_get_virtual_capability(dHandle, wi->data.name));
+                       break;
+               case HA_WURFL_DATA_TYPE_PROPERTY :
+                       ha_wurfl_log("WURFL: %s is a %s\n", wi->data.name, HA_WURFL_DATA_TYPE_PROPERTY_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+                       // write WURFL property type and name before its value...
+                       chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_PROPERTY_STRING, wi->data.name);
+#endif
+                       chunk_appendf(temp, "%s", wi->data.func_callback(global_wurfl.handle, dHandle));
+                       break;
+               }
+
+       }
+
+       wurfl_device_destroy(dHandle);
+       smp->data.u.str.area = temp->area;
+       smp->data.u.str.data = temp->data;
+       return 1;
+}
+
+static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+       wurfl_device_handle dHandle;
+       struct buffer *temp;
+       wurfl_data_t *wn = NULL;
+       struct ebmb_node *node;
+       ha_wurfl_header_t wh;
+       int i = 0;
+
+       ha_wurfl_log("WURFL: starting ha_wurfl_get\n");
+       wh.wsmp = smp;
+       dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh);
+
+       if (!dHandle) {
+               ha_wurfl_log("WURFL: unable to retrieve device from request %s\n", wurfl_get_error_message(global_wurfl.handle));
+               return 1;
+       }
+
+       temp = get_trash_chunk();
+       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);
+               wn = container_of(node, wurfl_data_t, nd);
+
+               if (wn) {
+
+                       switch(wn->type) {
+                       case HA_WURFL_DATA_TYPE_UNKNOWN :
+                               ha_wurfl_log("WURFL: %s is of an %s type\n", wn->name, HA_WURFL_DATA_TYPE_UNKNOWN_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+                               // write WURFL property type and name before its value...
+                               chunk_appendf(temp, "%s=%s", HA_WURFL_DATA_TYPE_UNKNOWN_STRING, wn->name);
+#endif
+                               break;
+                       case HA_WURFL_DATA_TYPE_CAP :
+                               ha_wurfl_log("WURFL: %s is a %s\n", wn->name, HA_WURFL_DATA_TYPE_CAP_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+                               // write WURFL property type and name before its value...
+                               chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_CAP_STRING, wn->name);
+#endif
+                               chunk_appendf(temp, "%s", wurfl_device_get_capability(dHandle, wn->name));
+                               break;
+                       case HA_WURFL_DATA_TYPE_VCAP :
+                               ha_wurfl_log("WURFL: %s is a %s\n", wn->name, HA_WURFL_DATA_TYPE_VCAP_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+                               // write WURFL property type and name before its value...
+                               chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_VCAP_STRING, wn->name);
+#endif
+                               chunk_appendf(temp, "%s", wurfl_device_get_virtual_capability(dHandle, wn->name));
+                               break;
+                       case HA_WURFL_DATA_TYPE_PROPERTY :
+                               ha_wurfl_log("WURFL: %s is a %s\n", wn->name, HA_WURFL_DATA_TYPE_PROPERTY_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+                               // write WURFL property type and name before its value...
+                               chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_PROPERTY_STRING, wn->name);
+#endif
+                               chunk_appendf(temp, "%s", wn->func_callback(global_wurfl.handle, dHandle));
+                               break;
+                       }
+
+               } else {
+                       ha_wurfl_log("WURFL: %s not in wurfl-information-list \n",
+                                    args[i].data.str.area);
+               }
+
+               i++;
+       }
+
+       wurfl_device_destroy(dHandle);
+       smp->data.u.str.area = temp->area;
+       smp->data.u.str.data = temp->data;
+       return 1;
+}
+
+static struct cfg_kw_list wurflcfg_kws = {{ }, {
+               { CFG_GLOBAL, "wurfl-data-file", ha_wurfl_cfg_data_file },
+               { CFG_GLOBAL, "wurfl-information-list-separator", ha_wurfl_cfg_information_list_separator },
+               { CFG_GLOBAL, "wurfl-information-list", ha_wurfl_cfg_information_list },
+               { CFG_GLOBAL, "wurfl-patch-file", ha_wurfl_cfg_patch_file_list },
+               { CFG_GLOBAL, "wurfl-cache-size", ha_wurfl_cfg_cache },
+               { CFG_GLOBAL, "wurfl-engine-mode", ha_wurfl_cfg_engine_mode },
+               { CFG_GLOBAL, "wurfl-useragent-priority", ha_wurfl_cfg_useragent_priority },
+               { 0, NULL, NULL },
+       }
+};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &wurflcfg_kws);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_fetch_kw_list fetch_kws = {ILH, {
+               { "wurfl-get-all", ha_wurfl_get_all, 0, NULL, SMP_T_STR, SMP_USE_HRQHV },
+               { "wurfl-get", ha_wurfl_get, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), NULL, SMP_T_STR, SMP_USE_HRQHV },
+               { NULL, NULL, 0, 0, 0 },
+       }
+};
+
+INITCALL1(STG_REGISTER, sample_register_fetches, &fetch_kws);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_conv_kw_list conv_kws = {ILH, {
+               { NULL, NULL, 0, 0, 0 },
+       }
+};
+
+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);
+}
+
+static const char *ha_wurfl_get_wurfl_id (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+       return wurfl_device_get_id(dHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_isdevroot (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+       if (wurfl_device_is_actual_device_root(dHandle))
+               return HA_WURFL_ISDEVROOT_TRUE;
+       else
+               return HA_WURFL_ISDEVROOT_FALSE;
+}
+
+static const char *ha_wurfl_get_wurfl_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+       return wurfl_device_get_original_useragent(dHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_api_version (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+       return wurfl_get_api_version();
+}
+
+static const char *ha_wurfl_get_wurfl_engine_target (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+       return wurfl_get_engine_target_as_string(wHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_info (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+       return wurfl_get_wurfl_info(wHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_last_load_time (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+       return wurfl_get_last_load_time_as_string(wHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_normalized_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+       return wurfl_device_get_normalized_useragent(dHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_useragent_priority (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+       return wurfl_get_useragent_priority_as_string(wHandle);
+}
+
+// call function for WURFL properties
+static const char *(*ha_wurfl_get_property_callback(char *name)) (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+       int position;
+       int begin = 0;
+       int end = HA_WURFL_PROPERTIES_NBR - 1;
+       int cond = 0;
+
+       while(begin <= end) {
+               position = (begin + end) / 2;
+
+               if((cond = strcmp(wurfl_properties_function_map[position].name, name)) == 0) {
+                       ha_wurfl_log("WURFL: ha_wurfl_get_property_callback match %s\n", wurfl_properties_function_map[position].name );
+                       return wurfl_properties_function_map[position].func;
+               } else if(cond < 0)
+                       begin = position + 1;
+               else
+                       end = position - 1;
+
+       }
+
+       return NULL;
+}
+
+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;
+       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;
+
+       if (http_find_full_header2(header_name, strlen(header_name), msg->chn->buf->p, idx, &ctx) == 0)
+               return 0;
+
+       if (header_len > ctx.vlen)
+               header_len = ctx.vlen;
+
+       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;
+}
+
+REGISTER_POST_CHECK(ha_wurfl_init);
+REGISTER_POST_DEINIT(ha_wurfl_deinit);
+REGISTER_BUILD_OPTS("Built with WURFL support.");