blob: eb1fb472dfb1cd86b435656d8ebdc9c09063bb79 [file] [log] [blame]
scientiamobiled0027ed2016-11-04 10:55:08 +01001#include <stdio.h>
2#include <stdarg.h>
3
4#include <common/cfgparse.h>
5#include <common/chunk.h>
6#include <common/buffer.h>
Willy Tarreaudc2ed472016-12-21 20:20:17 +01007#include <common/errors.h>
Willy Tarreau0108d902018-11-25 19:14:37 +01008#include <common/initcall.h>
scientiamobiled0027ed2016-11-04 10:55:08 +01009#include <proto/arg.h>
10#include <proto/log.h>
11#include <proto/proto_http.h>
12#include <proto/sample.h>
13#include <ebsttree.h>
14#include <ebmbtree.h>
scientiamobiled0027ed2016-11-04 10:55:08 +010015
Willy Tarreaue5d31692016-11-08 18:47:25 +010016#include <wurfl/wurfl.h>
17
Willy Tarreau350c1c62016-12-21 14:57:34 +010018static struct {
19 char *data_file; /* the WURFL data file */
20 char *cache_size; /* the WURFL cache parameters */
21 int engine_mode; /* the WURFL engine mode */
22 int useragent_priority; /* the WURFL ua priority */
23 struct list patch_file_list; /* the list of WURFL patch file to use */
24 char information_list_separator; /* the separator used in request to separate values */
25 struct list information_list; /* the list of WURFL data to return into request */
26 void *handle; /* the handle to WURFL engine */
27 struct eb_root btree; /* btree containing info (name/type) on WURFL data to return */
28} global_wurfl = {
29 .data_file = NULL,
30 .cache_size = NULL,
31 .engine_mode = -1,
32 .useragent_priority = -1,
33 .information_list_separator = ',',
34 .information_list = LIST_HEAD_INIT(global_wurfl.information_list),
35 .patch_file_list = LIST_HEAD_INIT(global_wurfl.patch_file_list),
36 .handle = NULL,
37};
scientiamobiled0027ed2016-11-04 10:55:08 +010038
39#ifdef WURFL_DEBUG
40inline static void ha_wurfl_log(char * message, ...)
41{
42 char logbuf[256];
43 va_list argp;
44
45 va_start(argp, message);
46 vsnprintf(logbuf, sizeof(logbuf), message, argp);
47 va_end(argp);
48 send_log(NULL, LOG_NOTICE, logbuf, NULL);
49}
50#else
51inline static void ha_wurfl_log(char * message, ...)
52{
53}
54#endif
55
56#define HA_WURFL_MAX_HEADER_LENGTH 1024
57
Willy Tarreaue5d31692016-11-08 18:47:25 +010058typedef char *(*PROP_CALLBACK_FUNC)(wurfl_handle wHandle, wurfl_device_handle dHandle);
59
60enum wurfl_data_type {
61 HA_WURFL_DATA_TYPE_UNKNOWN = 0,
62 HA_WURFL_DATA_TYPE_CAP = 100,
63 HA_WURFL_DATA_TYPE_VCAP = 200,
64 HA_WURFL_DATA_TYPE_PROPERTY = 300
65};
66
67typedef struct {
68 char *name;
69 enum wurfl_data_type type;
70 PROP_CALLBACK_FUNC func_callback;
71 struct ebmb_node nd;
72} wurfl_data_t;
73
scientiamobiled0027ed2016-11-04 10:55:08 +010074static const char HA_WURFL_MODULE_VERSION[] = "1.0";
75static const char HA_WURFL_ISDEVROOT_FALSE[] = "FALSE";
76static const char HA_WURFL_ISDEVROOT_TRUE[] = "TRUE";
77static const char HA_WURFL_TARGET_ACCURACY[] = "accuracy";
78static const char HA_WURFL_TARGET_PERFORMANCE[] = "performance";
79static const char HA_WURFL_PRIORITY_PLAIN[] = "plain";
80static const char HA_WURFL_PRIORITY_SIDELOADED_BROWSER[] = "sideloaded_browser";
81static const char HA_WURFL_MIN_ENGINE_VERSION_MANDATORY[] = "1.8.0.0";
82
83static const char HA_WURFL_DATA_TYPE_UNKNOWN_STRING[] = "unknown";
84static const char HA_WURFL_DATA_TYPE_CAP_STRING[] = "capability";
85static const char HA_WURFL_DATA_TYPE_VCAP_STRING[] = "virtual_capability";
86static const char HA_WURFL_DATA_TYPE_PROPERTY_STRING[] = "property";
87
88static const char *ha_wurfl_retrieve_header(const char *header_name, const void *wh);
89static const char *ha_wurfl_get_wurfl_root_id (wurfl_handle wHandle, wurfl_device_handle dHandle);
90static const char *ha_wurfl_get_wurfl_id (wurfl_handle wHandle, wurfl_device_handle dHandle);
91static const char *ha_wurfl_get_wurfl_isdevroot (wurfl_handle wHandle, wurfl_device_handle dHandle);
92static const char *ha_wurfl_get_wurfl_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle);
93static const char *ha_wurfl_get_wurfl_api_version (wurfl_handle wHandle, wurfl_device_handle dHandle);
94static const char *ha_wurfl_get_wurfl_engine_target (wurfl_handle wHandle, wurfl_device_handle dHandle);
95static const char *ha_wurfl_get_wurfl_info (wurfl_handle wHandle, wurfl_device_handle dHandle);
96static const char *ha_wurfl_get_wurfl_last_load_time (wurfl_handle wHandle, wurfl_device_handle dHandle);
97static const char *ha_wurfl_get_wurfl_normalized_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle);
98static const char *ha_wurfl_get_wurfl_useragent_priority (wurfl_handle wHandle, wurfl_device_handle dHandle);
99static const char *(*ha_wurfl_get_property_callback(char *name)) (wurfl_handle wHandle, wurfl_device_handle dHandle);
100
101// ordered property=>function map, suitable for binary search
102static const struct {
103 const char *name;
104 const char *(*func)(wurfl_handle wHandle, wurfl_device_handle dHandle);
105} wurfl_properties_function_map [] = {
106 {"wurfl_api_version", ha_wurfl_get_wurfl_api_version},
107 {"wurfl_engine_target", ha_wurfl_get_wurfl_engine_target},
108 {"wurfl_id", ha_wurfl_get_wurfl_id },
109 {"wurfl_info", ha_wurfl_get_wurfl_info },
110 {"wurfl_isdevroot", ha_wurfl_get_wurfl_isdevroot},
111 {"wurfl_last_load_time", ha_wurfl_get_wurfl_last_load_time},
112 {"wurfl_normalized_useragent", ha_wurfl_get_wurfl_normalized_useragent},
113 {"wurfl_useragent", ha_wurfl_get_wurfl_useragent},
114 {"wurfl_useragent_priority", ha_wurfl_get_wurfl_useragent_priority },
115 {"wurfl_root_id", ha_wurfl_get_wurfl_root_id},
116};
117static const int HA_WURFL_PROPERTIES_NBR = 10;
118
119typedef struct {
120 struct list list;
121 wurfl_data_t data;
122} wurfl_information_t;
123
124typedef struct {
125 struct list list;
126 char *patch_file_path;
127} wurfl_patches_t;
128
129typedef struct {
130 struct sample *wsmp;
131 char header_value[HA_WURFL_MAX_HEADER_LENGTH + 1];
132} ha_wurfl_header_t;
133
134/*
135 * configuration parameters parsing functions
136 */
137static int ha_wurfl_cfg_data_file(char **args, int section_type, struct proxy *curpx,
138 struct proxy *defpx, const char *file, int line,
139 char **err)
140{
141
142 if (*(args[1]) == 0) {
143 memprintf(err, "WURFL: %s expects a value.\n", args[0]);
144 return -1;
145 }
146
Willy Tarreau350c1c62016-12-21 14:57:34 +0100147 global_wurfl.data_file = strdup(args[1]);
scientiamobiled0027ed2016-11-04 10:55:08 +0100148 return 0;
149}
150
151static int ha_wurfl_cfg_cache(char **args, int section_type, struct proxy *curpx,
152 struct proxy *defpx, const char *file, int line,
153 char **err)
154{
155 if (*(args[1]) == 0) {
156 memprintf(err, "WURFL: %s expects a value.\n", args[0]);
157 return -1;
158 }
159
Willy Tarreau350c1c62016-12-21 14:57:34 +0100160 global_wurfl.cache_size = strdup(args[1]);
scientiamobiled0027ed2016-11-04 10:55:08 +0100161 return 0;
162}
163
164static int ha_wurfl_cfg_engine_mode(char **args, int section_type, struct proxy *curpx,
165 struct proxy *defpx, const char *file, int line,
166 char **err)
167{
168 if (*(args[1]) == 0) {
169 memprintf(err, "WURFL: %s expects a value.\n", args[0]);
170 return -1;
171 }
172
173 if (!strcmp(args[1],HA_WURFL_TARGET_ACCURACY)) {
Willy Tarreau350c1c62016-12-21 14:57:34 +0100174 global_wurfl.engine_mode = WURFL_ENGINE_TARGET_HIGH_ACCURACY;
scientiamobiled0027ed2016-11-04 10:55:08 +0100175 return 0;
176 }
177
178 if (!strcmp(args[1],HA_WURFL_TARGET_PERFORMANCE)) {
Willy Tarreau350c1c62016-12-21 14:57:34 +0100179 global_wurfl.engine_mode = WURFL_ENGINE_TARGET_HIGH_PERFORMANCE;
scientiamobiled0027ed2016-11-04 10:55:08 +0100180 return 0;
181 }
182
183 memprintf(err, "WURFL: %s valid values are %s or %s.\n", args[0], HA_WURFL_TARGET_PERFORMANCE, HA_WURFL_TARGET_ACCURACY);
184 return -1;
185}
186
187static int ha_wurfl_cfg_information_list_separator(char **args, int section_type, struct proxy *curpx,
188 struct proxy *defpx, const char *file, int line,
189 char **err)
190{
191 if (*(args[1]) == 0) {
192 memprintf(err, "WURFL: %s expects a single character.\n", args[0]);
193 return -1;
194 }
195
196 if (strlen(args[1]) > 1) {
197 memprintf(err, "WURFL: %s expects a single character, got %s.\n", args[0], args[1]);
198 return -1;
199 }
200
Willy Tarreau350c1c62016-12-21 14:57:34 +0100201 global_wurfl.information_list_separator = *args[1];
scientiamobiled0027ed2016-11-04 10:55:08 +0100202 return 0;
203}
204
205static int ha_wurfl_cfg_information_list(char **args, int section_type, struct proxy *curpx,
206 struct proxy *defpx, const char *file, int line,
207 char **err)
208{
209 int argIdx = 1;
210 wurfl_information_t *wi;
211
212 if (*(args[argIdx]) == 0) {
213 memprintf(err, "WURFL: %s expects a value.\n", args[0]);
214 return -1;
215 }
216
217 while (*(args[argIdx])) {
218 wi = calloc(1, sizeof(*wi));
219
220 if (wi == NULL) {
221 memprintf(err, "WURFL: Error allocating memory for %s element.\n", args[0]);
222 return -1;
223 }
224
225 wi->data.name = strdup(args[argIdx]);
226 wi->data.type = HA_WURFL_DATA_TYPE_UNKNOWN;
227 wi->data.func_callback = NULL;
Willy Tarreau350c1c62016-12-21 14:57:34 +0100228 LIST_ADDQ(&global_wurfl.information_list, &wi->list);
scientiamobiled0027ed2016-11-04 10:55:08 +0100229 ++argIdx;
230 }
231
232 return 0;
233}
234
235static int ha_wurfl_cfg_patch_file_list(char **args, int section_type, struct proxy *curpx,
236 struct proxy *defpx, const char *file, int line,
237 char **err)
238{
239 int argIdx = 1;
240 wurfl_patches_t *wp;
241
242 if (*(args[argIdx]) == 0) {
243 memprintf(err, "WURFL: %s expects a value.\n", args[0]);
244 return -1;
245 }
246
247 while (*(args[argIdx])) {
248 wp = calloc(1, sizeof(*wp));
249
250 if (wp == NULL) {
251 memprintf(err, "WURFL: Error allocating memory for %s element.\n", args[0]);
252 return -1;
253 }
254
255 wp->patch_file_path = strdup(args[argIdx]);
Willy Tarreau350c1c62016-12-21 14:57:34 +0100256 LIST_ADDQ(&global_wurfl.patch_file_list, &wp->list);
scientiamobiled0027ed2016-11-04 10:55:08 +0100257 ++argIdx;
258 }
259
260 return 0;
261}
262
263static int ha_wurfl_cfg_useragent_priority(char **args, int section_type, struct proxy *curpx,
264 struct proxy *defpx, const char *file, int line,
265 char **err)
266{
267 if (*(args[1]) == 0) {
268 memprintf(err, "WURFL: %s expects a value.\n", args[0]);
269 return -1;
270 }
271
272 if (!strcmp(args[1],HA_WURFL_PRIORITY_PLAIN)) {
Willy Tarreau350c1c62016-12-21 14:57:34 +0100273 global_wurfl.useragent_priority = WURFL_USERAGENT_PRIORITY_USE_PLAIN_USERAGENT;
scientiamobiled0027ed2016-11-04 10:55:08 +0100274 return 0;
275 }
276
277 if (!strcmp(args[1],HA_WURFL_PRIORITY_SIDELOADED_BROWSER)) {
Willy Tarreau350c1c62016-12-21 14:57:34 +0100278 global_wurfl.useragent_priority = WURFL_USERAGENT_PRIORITY_OVERRIDE_SIDELOADED_BROWSER_USERAGENT;
scientiamobiled0027ed2016-11-04 10:55:08 +0100279 return 0;
280 }
281
282 memprintf(err, "WURFL: %s valid values are %s or %s.\n", args[0], HA_WURFL_PRIORITY_PLAIN, HA_WURFL_PRIORITY_SIDELOADED_BROWSER);
283 return -1;
284}
285
286/*
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100287 * module init / deinit functions. Returns 0 if OK, or a combination of ERR_*.
scientiamobiled0027ed2016-11-04 10:55:08 +0100288 */
289
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100290static int ha_wurfl_init(void)
scientiamobiled0027ed2016-11-04 10:55:08 +0100291{
292 wurfl_information_t *wi;
293 wurfl_patches_t *wp;
294 wurfl_data_t * wn;
295 int wurfl_result_code = WURFL_OK;
296 int len;
297
298 send_log(NULL, LOG_NOTICE, "WURFL: Loading module v.%s\n", HA_WURFL_MODULE_VERSION);
299 // creating WURFL handler
Willy Tarreau350c1c62016-12-21 14:57:34 +0100300 global_wurfl.handle = wurfl_create();
scientiamobiled0027ed2016-11-04 10:55:08 +0100301
Willy Tarreau350c1c62016-12-21 14:57:34 +0100302 if (global_wurfl.handle == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100303 ha_warning("WURFL: Engine handler creation failed");
scientiamobiled0027ed2016-11-04 10:55:08 +0100304 send_log(NULL, LOG_WARNING, "WURFL: Engine handler creation failed\n");
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100305 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100306 }
307
308 send_log(NULL, LOG_NOTICE, "WURFL: Engine handler created - API version %s\n", wurfl_get_api_version() );
309
310 // set wurfl data file
Willy Tarreau350c1c62016-12-21 14:57:34 +0100311 if (global_wurfl.data_file == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100312 ha_warning("WURFL: missing wurfl-data-file parameter in global configuration\n");
scientiamobiled0027ed2016-11-04 10:55:08 +0100313 send_log(NULL, LOG_WARNING, "WURFL: missing wurfl-data-file parameter in global configuration\n");
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100314 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100315 }
316
Christopher Faulete8ca4342017-10-25 17:23:02 +0200317 if (global.nbthread > 1) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100318 ha_alert("WURFL: multithreading is not supported for now.\n");
Christopher Faulete8ca4342017-10-25 17:23:02 +0200319 return (ERR_FATAL | ERR_ALERT);
320 }
321
Willy Tarreau350c1c62016-12-21 14:57:34 +0100322 if (wurfl_set_root(global_wurfl.handle, global_wurfl.data_file) != WURFL_OK) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100323 ha_warning("WURFL: Engine setting root file failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreau350c1c62016-12-21 14:57:34 +0100324 send_log(NULL, LOG_WARNING, "WURFL: Engine setting root file failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100325 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100326 }
327
Willy Tarreau350c1c62016-12-21 14:57:34 +0100328 send_log(NULL, LOG_NOTICE, "WURFL: Engine root file set to %s\n", global_wurfl.data_file);
scientiamobiled0027ed2016-11-04 10:55:08 +0100329 // just a log to inform which separator char has to be used
Willy Tarreau350c1c62016-12-21 14:57:34 +0100330 send_log(NULL, LOG_NOTICE, "WURFL: Information list separator set to '%c'\n", global_wurfl.information_list_separator);
scientiamobiled0027ed2016-11-04 10:55:08 +0100331
332 // load wurfl data needed ( and filter whose are supposed to be capabilities )
Willy Tarreau350c1c62016-12-21 14:57:34 +0100333 if (LIST_ISEMPTY(&global_wurfl.information_list)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100334 ha_warning("WURFL: missing wurfl-information-list parameter in global configuration\n");
scientiamobiled0027ed2016-11-04 10:55:08 +0100335 send_log(NULL, LOG_WARNING, "WURFL: missing wurfl-information-list parameter in global configuration\n");
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100336 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100337 } else {
338 // ebtree initialization
Willy Tarreau350c1c62016-12-21 14:57:34 +0100339 global_wurfl.btree = EB_ROOT;
scientiamobiled0027ed2016-11-04 10:55:08 +0100340
341 // checking if informations are valid WURFL data ( cap, vcaps, properties )
Willy Tarreau350c1c62016-12-21 14:57:34 +0100342 list_for_each_entry(wi, &global_wurfl.information_list, list) {
scientiamobiled0027ed2016-11-04 10:55:08 +0100343 // check if information is already loaded looking into btree
Willy Tarreau350c1c62016-12-21 14:57:34 +0100344 if (ebst_lookup(&global_wurfl.btree, wi->data.name) == NULL) {
scientiamobiled0027ed2016-11-04 10:55:08 +0100345 if ((wi->data.func_callback = (PROP_CALLBACK_FUNC) ha_wurfl_get_property_callback(wi->data.name)) != NULL) {
346 wi->data.type = HA_WURFL_DATA_TYPE_PROPERTY;
347 ha_wurfl_log("WURFL: [%s] is a valid wurfl data [property]\n",wi->data.name);
Willy Tarreau350c1c62016-12-21 14:57:34 +0100348 } else if (wurfl_has_virtual_capability(global_wurfl.handle, wi->data.name)) {
scientiamobiled0027ed2016-11-04 10:55:08 +0100349 wi->data.type = HA_WURFL_DATA_TYPE_VCAP;
350 ha_wurfl_log("WURFL: [%s] is a valid wurfl data [virtual capability]\n",wi->data.name);
351 } else {
352 // by default a cap type is assumed to be and we control it on engine load
353 wi->data.type = HA_WURFL_DATA_TYPE_CAP;
354
Willy Tarreau350c1c62016-12-21 14:57:34 +0100355 if (wurfl_add_requested_capability(global_wurfl.handle, wi->data.name) != WURFL_OK) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100356 ha_warning("WURFL: capability filtering failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreau350c1c62016-12-21 14:57:34 +0100357 send_log(NULL, LOG_WARNING, "WURFL: capability filtering failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100358 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100359 }
360
361 ha_wurfl_log("WURFL: [%s] treated as wurfl capability. Will check its validity later, on engine load\n",wi->data.name);
362 }
363
364 // ebtree insert here
365 len = strlen(wi->data.name);
366
367 wn = malloc(sizeof(wurfl_data_t) + len + 1);
368
369 if (wn == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100370 ha_warning("WURFL: Error allocating memory for information tree element.\n");
scientiamobiled0027ed2016-11-04 10:55:08 +0100371 send_log(NULL, LOG_WARNING, "WURFL: Error allocating memory for information tree element.\n");
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100372 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100373 }
374
375 wn->name = wi->data.name;
376 wn->type = wi->data.type;
377 wn->func_callback = wi->data.func_callback;
378 memcpy(wn->nd.key, wi->data.name, len);
379 wn->nd.key[len] = 0;
380
Willy Tarreau350c1c62016-12-21 14:57:34 +0100381 if (!ebst_insert(&global_wurfl.btree, &wn->nd)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100382 ha_warning("WURFL: [%s] not inserted in btree\n",wn->name);
scientiamobiled0027ed2016-11-04 10:55:08 +0100383 send_log(NULL, LOG_WARNING, "WURFL: [%s] not inserted in btree\n",wn->name);
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100384 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100385 }
386
387 } else {
388 ha_wurfl_log("WURFL: [%s] already loaded\n",wi->data.name);
389 }
390
391 }
392
393 }
394
395 // filtering mandatory capabilities if engine version < 1.8.0.0
396 if (strcmp(wurfl_get_api_version(), HA_WURFL_MIN_ENGINE_VERSION_MANDATORY) < 0) {
397 wurfl_capability_enumerator_handle hmandatorycapabilityenumerator;
398 ha_wurfl_log("WURFL: Engine version %s < %s - Filtering mandatory capabilities\n", wurfl_get_api_version(), HA_WURFL_MIN_ENGINE_VERSION_MANDATORY);
Willy Tarreau350c1c62016-12-21 14:57:34 +0100399 hmandatorycapabilityenumerator = wurfl_get_mandatory_capability_enumerator(global_wurfl.handle);
scientiamobiled0027ed2016-11-04 10:55:08 +0100400
401 while (wurfl_capability_enumerator_is_valid(hmandatorycapabilityenumerator)) {
402 char *name = (char *)wurfl_capability_enumerator_get_name(hmandatorycapabilityenumerator);
403
Willy Tarreau350c1c62016-12-21 14:57:34 +0100404 if (ebst_lookup(&global_wurfl.btree, name) == NULL) {
405 if (wurfl_add_requested_capability(global_wurfl.handle, name) != WURFL_OK) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100406 ha_warning("WURFL: Engine adding mandatory capability [%s] failed - %s\n", name, wurfl_get_error_message(global_wurfl.handle));
Willy Tarreau350c1c62016-12-21 14:57:34 +0100407 send_log(NULL, LOG_WARNING, "WURFL: Adding mandatory capability [%s] failed - %s\n", name, wurfl_get_error_message(global_wurfl.handle));
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100408 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100409 }
410
411 ha_wurfl_log("WURFL: Mandatory capability [%s] added\n", name);
412 } else {
413 ha_wurfl_log("WURFL: Mandatory capability [%s] already filtered\n", name);
414 }
415
416 wurfl_capability_enumerator_move_next(hmandatorycapabilityenumerator);
417 }
418
419 wurfl_capability_enumerator_destroy(hmandatorycapabilityenumerator);
420 }
421
422 // adding WURFL patches if needed
Willy Tarreau350c1c62016-12-21 14:57:34 +0100423 if (!LIST_ISEMPTY(&global_wurfl.patch_file_list)) {
scientiamobiled0027ed2016-11-04 10:55:08 +0100424
Willy Tarreau350c1c62016-12-21 14:57:34 +0100425 list_for_each_entry(wp, &global_wurfl.patch_file_list, list) {
426 if (wurfl_add_patch(global_wurfl.handle, wp->patch_file_path) != WURFL_OK) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100427 ha_warning("WURFL: Engine adding patch file failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreau350c1c62016-12-21 14:57:34 +0100428 send_log(NULL, LOG_WARNING, "WURFL: Adding engine patch file failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100429 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100430 }
431 send_log(NULL, LOG_NOTICE, "WURFL: Engine patch file added %s\n", wp->patch_file_path);
432
433 }
434
435 }
436
437 // setting cache provider if specified in cfg, otherwise let engine choose
Willy Tarreau350c1c62016-12-21 14:57:34 +0100438 if (global_wurfl.cache_size != NULL) {
439 if (strpbrk(global_wurfl.cache_size, ",") != NULL) {
440 wurfl_result_code = wurfl_set_cache_provider(global_wurfl.handle, WURFL_CACHE_PROVIDER_DOUBLE_LRU, global_wurfl.cache_size) ;
scientiamobiled0027ed2016-11-04 10:55:08 +0100441 } else {
Willy Tarreau350c1c62016-12-21 14:57:34 +0100442 if (strcmp(global_wurfl.cache_size, "0")) {
443 wurfl_result_code = wurfl_set_cache_provider(global_wurfl.handle, WURFL_CACHE_PROVIDER_LRU, global_wurfl.cache_size) ;
scientiamobiled0027ed2016-11-04 10:55:08 +0100444 } else {
Willy Tarreau350c1c62016-12-21 14:57:34 +0100445 wurfl_result_code = wurfl_set_cache_provider(global_wurfl.handle, WURFL_CACHE_PROVIDER_NONE, 0);
scientiamobiled0027ed2016-11-04 10:55:08 +0100446 }
447
448 }
449
450 if (wurfl_result_code != WURFL_OK) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100451 ha_warning("WURFL: Setting cache to [%s] failed - %s\n", global_wurfl.cache_size, wurfl_get_error_message(global_wurfl.handle));
Willy Tarreau350c1c62016-12-21 14:57:34 +0100452 send_log(NULL, LOG_WARNING, "WURFL: Setting cache to [%s] failed - %s\n", global_wurfl.cache_size, wurfl_get_error_message(global_wurfl.handle));
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100453 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100454 }
455
Willy Tarreau350c1c62016-12-21 14:57:34 +0100456 send_log(NULL, LOG_NOTICE, "WURFL: Cache set to [%s]\n", global_wurfl.cache_size);
scientiamobiled0027ed2016-11-04 10:55:08 +0100457 }
458
459 // setting engine mode if specified in cfg, otherwise let engine choose
Willy Tarreau350c1c62016-12-21 14:57:34 +0100460 if (global_wurfl.engine_mode != -1) {
461 if (wurfl_set_engine_target(global_wurfl.handle, global_wurfl.engine_mode) != WURFL_OK ) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100462 ha_warning("WURFL: Setting engine target failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreau350c1c62016-12-21 14:57:34 +0100463 send_log(NULL, LOG_WARNING, "WURFL: Setting engine target failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100464 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100465 }
466
467 }
468
Willy Tarreau350c1c62016-12-21 14:57:34 +0100469 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) );
scientiamobiled0027ed2016-11-04 10:55:08 +0100470
471 // setting ua priority if specified in cfg, otherwise let engine choose
Willy Tarreau350c1c62016-12-21 14:57:34 +0100472 if (global_wurfl.useragent_priority != -1) {
473 if (wurfl_set_useragent_priority(global_wurfl.handle, global_wurfl.useragent_priority) != WURFL_OK ) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100474 ha_warning("WURFL: Setting engine useragent priority failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreau350c1c62016-12-21 14:57:34 +0100475 send_log(NULL, LOG_WARNING, "WURFL: Setting engine useragent priority failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100476 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100477 }
478
479 }
480
Willy Tarreau350c1c62016-12-21 14:57:34 +0100481 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) );
scientiamobiled0027ed2016-11-04 10:55:08 +0100482
483 // loading WURFL engine
Willy Tarreau350c1c62016-12-21 14:57:34 +0100484 if (wurfl_load(global_wurfl.handle) != WURFL_OK) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100485 ha_warning("WURFL: Engine load failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreau350c1c62016-12-21 14:57:34 +0100486 send_log(NULL, LOG_WARNING, "WURFL: Engine load failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100487 return ERR_WARN;
scientiamobiled0027ed2016-11-04 10:55:08 +0100488 }
489
490 send_log(NULL, LOG_NOTICE, "WURFL: Engine loaded\n");
491 send_log(NULL, LOG_NOTICE, "WURFL: Module load completed\n");
492 return 0;
493}
494
Willy Tarreau800f93f2016-12-21 20:52:38 +0100495static void ha_wurfl_deinit(void)
scientiamobiled0027ed2016-11-04 10:55:08 +0100496{
497 wurfl_information_t *wi, *wi2;
498 wurfl_patches_t *wp, *wp2;
499
500 send_log(NULL, LOG_NOTICE, "WURFL: Unloading module v.%s\n", HA_WURFL_MODULE_VERSION);
Willy Tarreau350c1c62016-12-21 14:57:34 +0100501 wurfl_destroy(global_wurfl.handle);
502 global_wurfl.handle = NULL;
503 free(global_wurfl.data_file);
504 global_wurfl.data_file = NULL;
505 free(global_wurfl.cache_size);
506 global_wurfl.cache_size = NULL;
scientiamobiled0027ed2016-11-04 10:55:08 +0100507
Willy Tarreau350c1c62016-12-21 14:57:34 +0100508 list_for_each_entry_safe(wi, wi2, &global_wurfl.information_list, list) {
scientiamobiled0027ed2016-11-04 10:55:08 +0100509 LIST_DEL(&wi->list);
510 free(wi);
511 }
512
Willy Tarreau350c1c62016-12-21 14:57:34 +0100513 list_for_each_entry_safe(wp, wp2, &global_wurfl.patch_file_list, list) {
scientiamobiled0027ed2016-11-04 10:55:08 +0100514 LIST_DEL(&wp->list);
515 free(wp);
516 }
517
518 send_log(NULL, LOG_NOTICE, "WURFL: Module unloaded\n");
519}
520
521static int ha_wurfl_get_all(const struct arg *args, struct sample *smp, const char *kw, void *private)
522{
523 wurfl_device_handle dHandle;
Willy Tarreau83061a82018-07-13 11:56:34 +0200524 struct buffer *temp;
scientiamobiled0027ed2016-11-04 10:55:08 +0100525 wurfl_information_t *wi;
526 ha_wurfl_header_t wh;
527
528 ha_wurfl_log("WURFL: starting ha_wurfl_get_all\n");
529 wh.wsmp = smp;
Willy Tarreau350c1c62016-12-21 14:57:34 +0100530 dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh);
scientiamobiled0027ed2016-11-04 10:55:08 +0100531
532 if (!dHandle) {
Willy Tarreau350c1c62016-12-21 14:57:34 +0100533 ha_wurfl_log("WURFL: unable to retrieve device from request %s\n", wurfl_get_error_message(global_wurfl.handle));
scientiamobiled0027ed2016-11-04 10:55:08 +0100534 return 1;
535 }
536
537 temp = get_trash_chunk();
538 chunk_reset(temp);
539
Willy Tarreau350c1c62016-12-21 14:57:34 +0100540 list_for_each_entry(wi, &global_wurfl.information_list, list) {
541 chunk_appendf(temp, "%c", global_wurfl.information_list_separator);
scientiamobiled0027ed2016-11-04 10:55:08 +0100542
543 switch(wi->data.type) {
544 case HA_WURFL_DATA_TYPE_UNKNOWN :
545 ha_wurfl_log("WURFL: %s is of an %s type\n", wi->data.name, HA_WURFL_DATA_TYPE_UNKNOWN_STRING);
546#ifdef WURFL_HEADER_WITH_DETAILS
547 // write WURFL property type and name before its value...
548 chunk_appendf(temp, "%s=%s", HA_WURFL_DATA_TYPE_UNKNOWN_STRING, wi->data.name);
549#endif
550 break;
551 case HA_WURFL_DATA_TYPE_CAP :
552 ha_wurfl_log("WURFL: %s is a %s\n", wi->data.name, HA_WURFL_DATA_TYPE_CAP_STRING);
553#ifdef WURFL_HEADER_WITH_DETAILS
554 // write WURFL property type and name before its value...
555 chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_CAP_STRING, wi->data.name);
556#endif
557 chunk_appendf(temp, "%s", wurfl_device_get_capability(dHandle, wi->data.name));
558 break;
559 case HA_WURFL_DATA_TYPE_VCAP :
560 ha_wurfl_log("WURFL: %s is a %s\n", wi->data.name, HA_WURFL_DATA_TYPE_VCAP_STRING);
561#ifdef WURFL_HEADER_WITH_DETAILS
562 // write WURFL property type and name before its value...
563 chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_VCAP_STRING, wi->data.name);
564#endif
565 chunk_appendf(temp, "%s", wurfl_device_get_virtual_capability(dHandle, wi->data.name));
566 break;
567 case HA_WURFL_DATA_TYPE_PROPERTY :
568 ha_wurfl_log("WURFL: %s is a %s\n", wi->data.name, HA_WURFL_DATA_TYPE_PROPERTY_STRING);
569#ifdef WURFL_HEADER_WITH_DETAILS
570 // write WURFL property type and name before its value...
571 chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_PROPERTY_STRING, wi->data.name);
572#endif
Willy Tarreau350c1c62016-12-21 14:57:34 +0100573 chunk_appendf(temp, "%s", wi->data.func_callback(global_wurfl.handle, dHandle));
scientiamobiled0027ed2016-11-04 10:55:08 +0100574 break;
575 }
576
577 }
578
579 wurfl_device_destroy(dHandle);
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200580 smp->data.u.str.area = temp->area;
581 smp->data.u.str.data = temp->data;
scientiamobiled0027ed2016-11-04 10:55:08 +0100582 return 1;
583}
584
585static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char *kw, void *private)
586{
587 wurfl_device_handle dHandle;
Willy Tarreau83061a82018-07-13 11:56:34 +0200588 struct buffer *temp;
scientiamobiled0027ed2016-11-04 10:55:08 +0100589 wurfl_data_t *wn = NULL;
590 struct ebmb_node *node;
591 ha_wurfl_header_t wh;
592 int i = 0;
593
594 ha_wurfl_log("WURFL: starting ha_wurfl_get\n");
595 wh.wsmp = smp;
Willy Tarreau350c1c62016-12-21 14:57:34 +0100596 dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh);
scientiamobiled0027ed2016-11-04 10:55:08 +0100597
598 if (!dHandle) {
Willy Tarreau350c1c62016-12-21 14:57:34 +0100599 ha_wurfl_log("WURFL: unable to retrieve device from request %s\n", wurfl_get_error_message(global_wurfl.handle));
scientiamobiled0027ed2016-11-04 10:55:08 +0100600 return 1;
601 }
602
603 temp = get_trash_chunk();
604 chunk_reset(temp);
605
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200606 while (args[i].data.str.area) {
Willy Tarreau350c1c62016-12-21 14:57:34 +0100607 chunk_appendf(temp, "%c", global_wurfl.information_list_separator);
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200608 node = ebst_lookup(&global_wurfl.btree, args[i].data.str.area);
scientiamobiled0027ed2016-11-04 10:55:08 +0100609 wn = container_of(node, wurfl_data_t, nd);
610
611 if (wn) {
612
613 switch(wn->type) {
614 case HA_WURFL_DATA_TYPE_UNKNOWN :
615 ha_wurfl_log("WURFL: %s is of an %s type\n", wn->name, HA_WURFL_DATA_TYPE_UNKNOWN_STRING);
616#ifdef WURFL_HEADER_WITH_DETAILS
617 // write WURFL property type and name before its value...
618 chunk_appendf(temp, "%s=%s", HA_WURFL_DATA_TYPE_UNKNOWN_STRING, wn->name);
619#endif
620 break;
621 case HA_WURFL_DATA_TYPE_CAP :
622 ha_wurfl_log("WURFL: %s is a %s\n", wn->name, HA_WURFL_DATA_TYPE_CAP_STRING);
623#ifdef WURFL_HEADER_WITH_DETAILS
624 // write WURFL property type and name before its value...
625 chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_CAP_STRING, wn->name);
626#endif
627 chunk_appendf(temp, "%s", wurfl_device_get_capability(dHandle, wn->name));
628 break;
629 case HA_WURFL_DATA_TYPE_VCAP :
630 ha_wurfl_log("WURFL: %s is a %s\n", wn->name, HA_WURFL_DATA_TYPE_VCAP_STRING);
631#ifdef WURFL_HEADER_WITH_DETAILS
632 // write WURFL property type and name before its value...
633 chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_VCAP_STRING, wn->name);
634#endif
635 chunk_appendf(temp, "%s", wurfl_device_get_virtual_capability(dHandle, wn->name));
636 break;
637 case HA_WURFL_DATA_TYPE_PROPERTY :
638 ha_wurfl_log("WURFL: %s is a %s\n", wn->name, HA_WURFL_DATA_TYPE_PROPERTY_STRING);
639#ifdef WURFL_HEADER_WITH_DETAILS
640 // write WURFL property type and name before its value...
641 chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_PROPERTY_STRING, wn->name);
642#endif
Willy Tarreau350c1c62016-12-21 14:57:34 +0100643 chunk_appendf(temp, "%s", wn->func_callback(global_wurfl.handle, dHandle));
scientiamobiled0027ed2016-11-04 10:55:08 +0100644 break;
645 }
646
647 } else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200648 ha_wurfl_log("WURFL: %s not in wurfl-information-list \n",
649 args[i].data.str.area);
scientiamobiled0027ed2016-11-04 10:55:08 +0100650 }
651
652 i++;
653 }
654
655 wurfl_device_destroy(dHandle);
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200656 smp->data.u.str.area = temp->area;
657 smp->data.u.str.data = temp->data;
scientiamobiled0027ed2016-11-04 10:55:08 +0100658 return 1;
659}
660
661static struct cfg_kw_list wurflcfg_kws = {{ }, {
662 { CFG_GLOBAL, "wurfl-data-file", ha_wurfl_cfg_data_file },
663 { CFG_GLOBAL, "wurfl-information-list-separator", ha_wurfl_cfg_information_list_separator },
664 { CFG_GLOBAL, "wurfl-information-list", ha_wurfl_cfg_information_list },
665 { CFG_GLOBAL, "wurfl-patch-file", ha_wurfl_cfg_patch_file_list },
666 { CFG_GLOBAL, "wurfl-cache-size", ha_wurfl_cfg_cache },
667 { CFG_GLOBAL, "wurfl-engine-mode", ha_wurfl_cfg_engine_mode },
668 { CFG_GLOBAL, "wurfl-useragent-priority", ha_wurfl_cfg_useragent_priority },
669 { 0, NULL, NULL },
670 }
671};
672
Willy Tarreau0108d902018-11-25 19:14:37 +0100673INITCALL1(STG_REGISTER, cfg_register_keywords, &wurflcfg_kws);
674
scientiamobiled0027ed2016-11-04 10:55:08 +0100675/* Note: must not be declared <const> as its list will be overwritten */
676static struct sample_fetch_kw_list fetch_kws = {ILH, {
677 { "wurfl-get-all", ha_wurfl_get_all, 0, NULL, SMP_T_STR, SMP_USE_HRQHV },
678 { "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 },
679 { NULL, NULL, 0, 0, 0 },
680 }
681};
682
Willy Tarreau0108d902018-11-25 19:14:37 +0100683INITCALL1(STG_REGISTER, sample_register_fetches, &fetch_kws);
684
scientiamobiled0027ed2016-11-04 10:55:08 +0100685/* Note: must not be declared <const> as its list will be overwritten */
686static struct sample_conv_kw_list conv_kws = {ILH, {
687 { NULL, NULL, 0, 0, 0 },
688 }
689};
690
Willy Tarreau0108d902018-11-25 19:14:37 +0100691INITCALL1(STG_REGISTER, sample_register_convs, &conv_kws);
692
scientiamobiled0027ed2016-11-04 10:55:08 +0100693__attribute__((constructor))
694static void __wurfl_init(void)
695{
696 /* register sample fetch and format conversion keywords */
Willy Tarreau770042d2016-12-21 18:47:13 +0100697 hap_register_build_opts("Built with WURFL support.", 0);
Willy Tarreaudc2ed472016-12-21 20:20:17 +0100698 hap_register_post_check(ha_wurfl_init);
Willy Tarreau800f93f2016-12-21 20:52:38 +0100699 hap_register_post_deinit(ha_wurfl_deinit);
scientiamobiled0027ed2016-11-04 10:55:08 +0100700}
701
702// WURFL properties wrapper functions
703static const char *ha_wurfl_get_wurfl_root_id (wurfl_handle wHandle, wurfl_device_handle dHandle)
704{
705 return wurfl_device_get_root_id(dHandle);
706}
707
708static const char *ha_wurfl_get_wurfl_id (wurfl_handle wHandle, wurfl_device_handle dHandle)
709{
710 return wurfl_device_get_id(dHandle);
711}
712
713static const char *ha_wurfl_get_wurfl_isdevroot (wurfl_handle wHandle, wurfl_device_handle dHandle)
714{
715 if (wurfl_device_is_actual_device_root(dHandle))
716 return HA_WURFL_ISDEVROOT_TRUE;
717 else
718 return HA_WURFL_ISDEVROOT_FALSE;
719}
720
721static const char *ha_wurfl_get_wurfl_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle)
722{
723 return wurfl_device_get_original_useragent(dHandle);
724}
725
726static const char *ha_wurfl_get_wurfl_api_version (wurfl_handle wHandle, wurfl_device_handle dHandle)
727{
728 return wurfl_get_api_version();
729}
730
731static const char *ha_wurfl_get_wurfl_engine_target (wurfl_handle wHandle, wurfl_device_handle dHandle)
732{
733 return wurfl_get_engine_target_as_string(wHandle);
734}
735
736static const char *ha_wurfl_get_wurfl_info (wurfl_handle wHandle, wurfl_device_handle dHandle)
737{
738 return wurfl_get_wurfl_info(wHandle);
739}
740
741static const char *ha_wurfl_get_wurfl_last_load_time (wurfl_handle wHandle, wurfl_device_handle dHandle)
742{
743 return wurfl_get_last_load_time_as_string(wHandle);
744}
745
746static const char *ha_wurfl_get_wurfl_normalized_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle)
747{
748 return wurfl_device_get_normalized_useragent(dHandle);
749}
750
751static const char *ha_wurfl_get_wurfl_useragent_priority (wurfl_handle wHandle, wurfl_device_handle dHandle)
752{
753 return wurfl_get_useragent_priority_as_string(wHandle);
754}
755
756// call function for WURFL properties
757static const char *(*ha_wurfl_get_property_callback(char *name)) (wurfl_handle wHandle, wurfl_device_handle dHandle)
758{
759 int position;
760 int begin = 0;
761 int end = HA_WURFL_PROPERTIES_NBR - 1;
762 int cond = 0;
763
764 while(begin <= end) {
765 position = (begin + end) / 2;
766
767 if((cond = strcmp(wurfl_properties_function_map[position].name, name)) == 0) {
768 ha_wurfl_log("WURFL: ha_wurfl_get_property_callback match %s\n", wurfl_properties_function_map[position].name );
769 return wurfl_properties_function_map[position].func;
770 } else if(cond < 0)
771 begin = position + 1;
772 else
773 end = position - 1;
774
775 }
776
777 return NULL;
778}
779
780static const char *ha_wurfl_retrieve_header(const char *header_name, const void *wh)
781{
782 struct sample *smp;
783 struct hdr_idx *idx;
784 struct hdr_ctx ctx;
785 const struct http_msg *msg;
786 int header_len = HA_WURFL_MAX_HEADER_LENGTH;
787
788 ha_wurfl_log("WURFL: retrieve header request [%s]\n", header_name);
789 smp = ((ha_wurfl_header_t *)wh)->wsmp;
790 idx = &smp->strm->txn->hdr_idx;
791 msg = &smp->strm->txn->req;
792 ctx.idx = 0;
793
794 if (http_find_full_header2(header_name, strlen(header_name), msg->chn->buf->p, idx, &ctx) == 0)
795 return 0;
796
797 if (header_len > ctx.vlen)
798 header_len = ctx.vlen;
799
800 strncpy(((ha_wurfl_header_t *)wh)->header_value, ctx.line + ctx.val, header_len);
801 ((ha_wurfl_header_t *)wh)->header_value[header_len] = '\0';
802 ha_wurfl_log("WURFL: retrieve header request returns [%s]\n", ((ha_wurfl_header_t *)wh)->header_value);
803 return ((ha_wurfl_header_t *)wh)->header_value;
804}