mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-09-03 21:12:06 +02:00
At the moment the WURFL module emits 3 lines of warnings upon startup when it is not referenced in the configuration file, which is quite confusing. Let's make sure to keep it silent when not configured, as detected by the absence of the wurfl-data-file statement.
835 lines
26 KiB
C
835 lines
26 KiB
C
#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/http_fetch.h>
|
|
#include <proto/http_htx.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 */
|
|
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,
|
|
.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[] = "2.0";
|
|
static const char HA_WURFL_ISDEVROOT_FALSE[] = "FALSE";
|
|
static const char HA_WURFL_ISDEVROOT_TRUE[] = "TRUE";
|
|
|
|
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}, // kept for backward conf file compat
|
|
{"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_root_id", ha_wurfl_get_wurfl_root_id},
|
|
{"wurfl_useragent", ha_wurfl_get_wurfl_useragent},
|
|
{"wurfl_useragent_priority", ha_wurfl_get_wurfl_useragent_priority }, // kept for backward conf file compat
|
|
};
|
|
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)
|
|
{
|
|
// kept for backward conf file compat
|
|
return 0;
|
|
}
|
|
|
|
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)
|
|
{
|
|
// this feature is deprecated, keeping only not to break compatibility
|
|
// with old configuration files.
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
// wurfl-data-file not configured, WURFL is not used so don't try to
|
|
// configure it.
|
|
if (global_wurfl.data_file == NULL)
|
|
return 0;
|
|
|
|
ha_notice("WURFL: Loading module v.%s\n", HA_WURFL_MODULE_VERSION);
|
|
// creating WURFL handler
|
|
global_wurfl.handle = wurfl_create();
|
|
|
|
if (global_wurfl.handle == NULL) {
|
|
ha_warning("WURFL: Engine handler creation failed\n");
|
|
return ERR_WARN;
|
|
}
|
|
|
|
ha_notice("WURFL: Engine handler created - API version %s\n", wurfl_get_api_version() );
|
|
|
|
// set wurfl data file
|
|
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));
|
|
return ERR_WARN;
|
|
}
|
|
|
|
ha_notice("WURFL: Engine root file set to %s\n", global_wurfl.data_file);
|
|
// just a log to inform which separator char has to be used
|
|
ha_notice("WURFL: Information list separator set to '%c'\n", global_wurfl.information_list_separator);
|
|
|
|
// load wurfl data needed ( and filter whose are supposed to be capabilities )
|
|
if (LIST_ISEMPTY(&global_wurfl.information_list)) {
|
|
ha_warning("WURFL: missing wurfl-information-list parameter in global configuration\n");
|
|
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;
|
|
#ifdef WURFL_DEBUG
|
|
ha_notice("WURFL: [%s] is a valid wurfl data [property]\n",wi->data.name);
|
|
#endif
|
|
} else if (wurfl_has_virtual_capability(global_wurfl.handle, wi->data.name)) {
|
|
wi->data.type = HA_WURFL_DATA_TYPE_VCAP;
|
|
#ifdef WURFL_DEBUG
|
|
ha_notice("WURFL: [%s] is a valid wurfl data [virtual capability]\n",wi->data.name);
|
|
#endif
|
|
} else {
|
|
// by default a cap type is assumed to be and we control it on engine load
|
|
wi->data.type = HA_WURFL_DATA_TYPE_CAP;
|
|
|
|
if (wurfl_add_requested_capability(global_wurfl.handle, wi->data.name) != WURFL_OK) {
|
|
ha_warning("WURFL: capability filtering failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
|
|
return ERR_WARN;
|
|
}
|
|
|
|
ha_notice("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");
|
|
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);
|
|
return ERR_WARN;
|
|
}
|
|
|
|
} else {
|
|
#ifdef WURFL_DEBUG
|
|
ha_notice("WURFL: [%s] already loaded\n",wi->data.name);
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// 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));
|
|
return ERR_WARN;
|
|
}
|
|
ha_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));
|
|
return ERR_WARN;
|
|
}
|
|
|
|
ha_notice("WURFL: Cache set to [%s]\n", global_wurfl.cache_size);
|
|
}
|
|
|
|
// loading WURFL engine
|
|
if (wurfl_load(global_wurfl.handle) != WURFL_OK) {
|
|
ha_warning("WURFL: Engine load failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
|
|
return ERR_WARN;
|
|
}
|
|
|
|
ha_notice("WURFL: Engine loaded\n");
|
|
ha_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;
|
|
struct channel *chn;
|
|
|
|
ha_wurfl_log("WURFL: starting ha_wurfl_get_all\n");
|
|
|
|
chn = (smp->strm ? &smp->strm->req : NULL);
|
|
|
|
if (smp->px->options2 & PR_O2_USE_HTX) {
|
|
/* HTX version */
|
|
struct htx *htx = smp_prefetch_htx(smp, chn, 1);
|
|
|
|
if (!htx) {
|
|
return 0;
|
|
}
|
|
|
|
} else {
|
|
/* Legacy version */
|
|
CHECK_HTTP_MESSAGE_FIRST(chn);
|
|
}
|
|
|
|
wh.wsmp = smp;
|
|
|
|
dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh);
|
|
|
|
temp = get_trash_chunk();
|
|
chunk_reset(temp);
|
|
|
|
if (!dHandle) {
|
|
ha_wurfl_log("WURFL: unable to retrieve device from request %s\n", wurfl_get_error_message(global_wurfl.handle));
|
|
goto wurfl_get_all_completed;
|
|
}
|
|
|
|
list_for_each_entry(wi, &global_wurfl.information_list, list) {
|
|
|
|
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;
|
|
}
|
|
|
|
// append wurfl-information-list-separator
|
|
chunk_appendf(temp, "%c", global_wurfl.information_list_separator);
|
|
}
|
|
|
|
wurfl_get_all_completed:
|
|
|
|
wurfl_device_destroy(dHandle);
|
|
smp->data.u.str.area = temp->area;
|
|
smp->data.u.str.data = temp->data;
|
|
|
|
// remove trailing wurfl-information-list-separator
|
|
if (temp->data) {
|
|
temp->area[temp->data] = '\0';
|
|
--smp->data.u.str.data;
|
|
}
|
|
|
|
smp->data.type = SMP_T_STR;
|
|
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;
|
|
struct channel *chn;
|
|
|
|
ha_wurfl_log("WURFL: starting ha_wurfl_get\n");
|
|
|
|
chn = (smp->strm ? &smp->strm->req : NULL);
|
|
|
|
if (smp->px->options2 & PR_O2_USE_HTX) {
|
|
/* HTX version */
|
|
struct htx *htx = smp_prefetch_htx(smp, chn, 1);
|
|
|
|
if (!htx) {
|
|
return 0;
|
|
}
|
|
|
|
} else {
|
|
/* Legacy version */
|
|
CHECK_HTTP_MESSAGE_FIRST(chn);
|
|
}
|
|
|
|
wh.wsmp = smp;
|
|
|
|
dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh);
|
|
|
|
temp = get_trash_chunk();
|
|
chunk_reset(temp);
|
|
|
|
if (!dHandle) {
|
|
ha_wurfl_log("WURFL: unable to retrieve device from request %s\n", wurfl_get_error_message(global_wurfl.handle));
|
|
goto wurfl_get_completed;
|
|
}
|
|
|
|
while (args[i].data.str.area) {
|
|
node = ebst_lookup(&global_wurfl.btree, args[i].data.str.area);
|
|
|
|
if (node) {
|
|
|
|
wn = container_of(node, wurfl_data_t, nd);
|
|
|
|
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;
|
|
}
|
|
|
|
// append wurfl-information-list-separator
|
|
chunk_appendf(temp, "%c", global_wurfl.information_list_separator);
|
|
|
|
} else {
|
|
ha_wurfl_log("WURFL: %s not in wurfl-information-list \n",
|
|
args[i].data.str.area);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
wurfl_get_completed:
|
|
|
|
wurfl_device_destroy(dHandle);
|
|
smp->data.u.str.area = temp->area;
|
|
smp->data.u.str.data = temp->data;
|
|
|
|
// remove trailing wurfl-information-list-separator
|
|
if (temp->data) {
|
|
temp->area[temp->data] = '\0';
|
|
--smp->data.u.str.data;
|
|
}
|
|
|
|
smp->data.type = SMP_T_STR;
|
|
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)
|
|
{
|
|
if (wurfl_device_get_root_id(dHandle))
|
|
return wurfl_device_get_root_id(dHandle);
|
|
else
|
|
return "";
|
|
}
|
|
|
|
static const char *ha_wurfl_get_wurfl_id (wurfl_handle wHandle, wurfl_device_handle dHandle)
|
|
{
|
|
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 "default";
|
|
}
|
|
|
|
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 "default";
|
|
}
|
|
|
|
// 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 channel *chn;
|
|
int header_len = HA_WURFL_MAX_HEADER_LENGTH;
|
|
|
|
smp = ((ha_wurfl_header_t *)wh)->wsmp;
|
|
chn = (smp->strm ? &smp->strm->req : NULL);
|
|
|
|
if (smp->px->options2 & PR_O2_USE_HTX) {
|
|
/* HTX version */
|
|
struct htx *htx;
|
|
struct http_hdr_ctx ctx;
|
|
struct ist name;
|
|
|
|
ha_wurfl_log("WURFL: retrieve header (HTX) request [%s]\n", header_name);
|
|
|
|
//the header is searched from the beginning
|
|
ctx.blk = NULL;
|
|
|
|
// We could skip this chek since ha_wurfl_retrieve_header is called from inside
|
|
// ha_wurfl_get()/ha_wurfl_get_all() that already perform the same check
|
|
// We choose to keep it in case ha_wurfl_retrieve_header will be called directly
|
|
htx = smp_prefetch_htx(smp, chn, 1);
|
|
if (!htx) {
|
|
return NULL;
|
|
}
|
|
|
|
name.ptr = (char *)header_name;
|
|
name.len = strlen(header_name);
|
|
|
|
// If 4th param is set, it works on full-line headers in whose comma is not a delimiter but is
|
|
// part of the syntax
|
|
if (!http_find_header(htx, name, &ctx, 1)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (header_len > ctx.value.len)
|
|
header_len = ctx.value.len;
|
|
|
|
strncpy(((ha_wurfl_header_t *)wh)->header_value, ctx.value.ptr, header_len);
|
|
|
|
} else {
|
|
/* Legacy version */
|
|
struct http_txn *txn;
|
|
struct hdr_idx *idx;
|
|
struct hdr_ctx ctx;
|
|
int res;
|
|
|
|
ha_wurfl_log("WURFL: retrieve header (legacy) request [%s]\n", header_name);
|
|
|
|
// We could skip this chek since ha_wurfl_retrieve_header is called from inside
|
|
// ha_wurfl_get()/ha_wurfl_get_all() that already perform the same check
|
|
// We choose to keep it in case ha_wurfl_retrieve_header will be called directly
|
|
// This is a version of CHECK_HTTP_MESSAGE_FIRST(chn) which returns NULL in case of error
|
|
res = smp_prefetch_http(smp->px, smp->strm, smp->opt, (chn), smp, 1);
|
|
if (res <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
txn = smp->strm->txn;
|
|
idx = &txn->hdr_idx;
|
|
|
|
ctx.idx = 0;
|
|
|
|
if (http_find_full_header2(header_name, strlen(header_name), ci_head(chn), idx, &ctx) == 0)
|
|
return NULL;
|
|
|
|
if (header_len > ctx.vlen)
|
|
header_len = ctx.vlen;
|
|
|
|
strncpy(((ha_wurfl_header_t *)wh)->header_value, ctx.line + ctx.val, header_len);
|
|
|
|
}
|
|
|
|
((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;
|
|
}
|
|
|
|
static void ha_wurfl_register_build_options()
|
|
{
|
|
const char *ver = wurfl_get_api_version();
|
|
char *ptr = NULL;
|
|
|
|
memprintf(&ptr, "Built with WURFL support (%sversion %s)",
|
|
strcmp(ver, "1.11.2.100") ? "" : "dummy library ",
|
|
ver);
|
|
hap_register_build_opts(ptr, 1);
|
|
}
|
|
|
|
REGISTER_POST_CHECK(ha_wurfl_init);
|
|
REGISTER_POST_DEINIT(ha_wurfl_deinit);
|
|
INITCALL0(STG_REGISTER, ha_wurfl_register_build_options);
|