diff --git a/Makefile b/Makefile index b4afcfb39..976524d26 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ # USE_DL : enable it if your system requires -ldl. 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 # # Options can be forced by specifying "USE_xxx=1" or can be disabled by using # "USE_xxx=" (empty string). @@ -648,6 +649,24 @@ BUILD_OPTIONS += $(call ignore_implicit,USE_51DEGREES) 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_PCRE)$(USE_STATIC_PCRE)$(USE_PCRE_JIT),) # PCREDIR is used to automatically construct the PCRE_INC and PCRE_LIB paths, # by appending /include and /lib respectively. If your system does not use the diff --git a/README b/README index 6a13704f6..79283086b 100644 --- a/README +++ b/README @@ -409,6 +409,11 @@ page: https://51degrees.com/compare-data-options +1.3) Scientiamobile WURFL Device Detection +------------------------------- + +Please see doc/WURFL-device-detection.txt + 2) How to install it -------------------- diff --git a/doc/WURFL-device-detection.txt b/doc/WURFL-device-detection.txt new file mode 100644 index 000000000..8b44813f8 --- /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 54555aaf3..01a07646d 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -561,6 +561,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 @@ -990,6 +996,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 000000000..ad651a8cd --- /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/include/import/wurfl.h b/include/import/wurfl.h new file mode 100644 index 000000000..c59da34dd --- /dev/null +++ b/include/import/wurfl.h @@ -0,0 +1,25 @@ +#ifndef _IMPORT_WURFL_H +#define _IMPORT_WURFL_H + +#include + +int ha_wurfl_init(void); +void ha_wurfl_deinit(void); + +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; + +#endif diff --git a/include/types/global.h b/include/types/global.h index 2a9bf5636..1e8cabaa7 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -36,6 +36,10 @@ #include #endif +#ifdef USE_WURFL +#include +#endif + #ifndef UNIX_MAX_PATH #define UNIX_MAX_PATH 108 #endif @@ -209,6 +213,19 @@ struct global { int cache_size; } _51degrees; #endif +#ifdef USE_WURFL + 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 */ + wurfl_handle handle; /* the handle to WURFL engine */ + struct eb_root btree; /* btree containing info (name/type) on WURFL data to return */ + } wurfl; +#endif }; extern struct global global; diff --git a/src/haproxy.c b/src/haproxy.c index 2b12ba3bf..222ee20a5 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -122,6 +122,10 @@ #include #endif +#ifdef USE_WURFL +#include +#endif + /*********************************************************************/ extern const struct comp_algo comp_algos[]; @@ -204,6 +208,19 @@ struct global global = { .cache_size = 0, }, #endif +#ifdef USE_WURFL + .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, + }, +#endif + /* others NULL OK */ }; @@ -413,6 +430,9 @@ void display_build_opts() #endif #ifdef USE_51DEGREES printf("Built with 51Degrees support\n"); +#endif +#ifdef USE_WURFL + printf("Built with WURFL support\n"); #endif putchar('\n'); @@ -977,6 +997,9 @@ void init(int argc, char **argv) #ifdef USE_51DEGREES init_51degrees(); #endif +#ifdef USE_WURFL + ha_wurfl_init(); +#endif for (px = proxy; px; px = px->next) { err_code |= flt_init(px); @@ -1647,6 +1670,10 @@ void deinit(void) deinit_51degrees(); #endif +#ifdef USE_WURFL + ha_wurfl_deinit(); +#endif + free(global.log_send_hostname); global.log_send_hostname = NULL; chunk_destroy(&global.log_tag); free(global.chroot); global.chroot = NULL; diff --git a/src/wurfl.c b/src/wurfl.c new file mode 100644 index 000000000..0f5ce2d7f --- /dev/null +++ b/src/wurfl.c @@ -0,0 +1,761 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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 + +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 + */ + +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) { + Warning("WURFL: Engine handler creation failed"); + send_log(NULL, LOG_WARNING, "WURFL: Engine handler creation failed\n"); + return -1; + } + + 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) { + 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 -1; + } + + if (wurfl_set_root(global.wurfl.handle, global.wurfl.data_file) != WURFL_OK) { + 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 -1; + } + + 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)) { + 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 -1; + } 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) { + 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 -1; + } + + 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) { + 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 -1; + } + + 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)) { + 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 -1; + } + + } 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) { + 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 -1; + } + + 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) { + 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 -1; + } + 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) { + 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 -1; + } + + 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 ) { + 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 -1; + } + + } + + 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 ) { + 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 -1; + } + + } + + 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) { + 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 -1; + } + + send_log(NULL, LOG_NOTICE, "WURFL: Engine loaded\n"); + send_log(NULL, LOG_NOTICE, "WURFL: Module load completed\n"); + return 0; +} + +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 chunk *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.str = temp->str; + smp->data.u.str.len = temp->len; + return 1; +} + +static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + wurfl_device_handle dHandle; + struct chunk *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.str) { + chunk_appendf(temp, "%c", global.wurfl.information_list_separator); + node = ebst_lookup(&global.wurfl.btree, args[i].data.str.str); + 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.str); + } + + i++; + } + + wurfl_device_destroy(dHandle); + smp->data.u.str.str = temp->str; + smp->data.u.str.len = temp->len; + 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 }, + } +}; + +/* 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 }, + } +}; + +/* 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 }, + } +}; + +__attribute__((constructor)) +static void __wurfl_init(void) +{ + /* register sample fetch and format conversion keywords */ + sample_register_fetches(&fetch_kws); + sample_register_convs(&conv_kws); + cfg_register_keywords(&wurflcfg_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; +}