From: Willy Tarreau Date: Fri, 19 Apr 2019 14:03:32 +0000 (+0200) Subject: Revert "CLEANUP: wurfl: remove dead, broken and unmaintained code" X-Git-Tag: v2.0-dev3~204 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b3cc9f28874e31680c041d1fe8a8da2ac2a53a9d;p=thirdparty%2Fhaproxy.git Revert "CLEANUP: wurfl: remove dead, broken and unmaintained code" This reverts commit 8e5e1e7bf000f2603a085c76be118214d22c55b4. The following patches will fix this code and may be backported. --- diff --git a/INSTALL b/INSTALL index b7a8c4c0fa..9df17caf68 100644 --- 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 diff --git a/Makefile b/Makefile index 35772be90d..e7a9db0761 100644 --- 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 index 0000000000..c968615132 --- /dev/null +++ b/doc/WURFL-device-detection.txt @@ -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= 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 +- wurfl-information-list [] (list of WURFL capabilities, + virtual capabilities, property names we plan to use in injected headers) +- wurfl-information-list-separator (character that will be + used to separate values in a response header, ',' by default). +- wurfl-engine-mode (Sets the WURFL engine target. You can choose + between "accuracy" and "performance","performance" by default) +- wurfl-cache-size (Sets the WURFL caching strategy) +- wurfl-patch-file [] (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 + + ... + 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 diff --git a/doc/configuration.txt b/doc/configuration.txt index 47edf38468..bae1979997 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 Please note that this option is only available when haproxy has been compiled with USE_51DEGREES. +wurfl-data-file + 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 []* + 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 + 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 [] + 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 [,] + Sets the WURFL caching strategy. Here is the Useragent cache size, and + is the internal device cache size. There are three possibilities here : + - "0" : no cache is used. + - : the Single LRU cache is used, the size is expressed in elements. + - , : 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 index 0000000000..ad651a8cd4 --- /dev/null +++ b/examples/wurfl-example.cfg @@ -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 index 0000000000..75dcf0045f --- /dev/null +++ b/src/wurfl.c @@ -0,0 +1,800 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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 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 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.");