1
2
3 """
4 @copyright:
5 Copyright (c) 2014 by mTLD Top Level Domain Limited. All rights reserved.\n
6 Portions copyright (c) 2008 by Argo Interactive Limited.\n
7 Portions copyright (c) 2008 by Nokia Inc.\n
8 Portions copyright (c) 2008 by Telecom Italia Mobile S.p.A.\n
9 Portions copyright (c) 2008 by Volantis Systems Limited.\n
10 Portions copyright (c) 2002-2008 by Andreas Staeding.\n
11 Portions copyright (c) 2008 by Zandan.\n
12 @author: dotMobi
13 """
14
15 import re
16 from mobi.mtld.da.device.post_walk_rules import PostWalkRules
17 from mobi.mtld.da.property import Property
18
20 """
21 This class tries to extract properties from the User-Agent string itself. This
22 is a completely separate step to the main JSON tree walk but uses the results
23 of the tree walk to optimise the property extraction. The property extraction
24 is done in two steps.
25
26 Step 1: Try and identify the type of User-Agent and thus the set of property
27 extraction rules to run. This is optimised by the properties from the tree
28 walk.
29
30 Step 2: Run the rules found in step 1 to try and extract the properties.
31 """
32
33 API_ID = 5
34
35
36 KEY_UA_RULES = 'uar'
37 KEY_SKIP_IDS = 'sk'
38 KEY_DEFAULT_REGEX_SET = 'd'
39 KEY_RULE_GROUPS = 'rg'
40 KEY_RULE_REGEX_ID = 'r'
41 KEY_REGEXES = 'reg'
42 KEY_REGEX_MATCH_POS = 'm'
43 KEY_REFINE_REGEX_ID = 'f'
44 KEY_SEARCH_REGEX_ID = 's'
45
51
73
74
75
77 """
78 Find all the properties that are used for matching.
79
80 @param group: The rule group that can contain a property matcher
81 @param prop_ids: The list of found property IDs
82 """
83
84 if group[self.KEY_PROPERTY_MATCHER]:
85 for prop_id, prop_value in group[self.KEY_PROPERTY_MATCHER].items():
86 if prop_id not in prop_ids:
87 prop_ids.append(prop_id)
88
90 """
91 Prepare the rule set by extracting it from the current group and counting
92 the items in the group. This is done to avoid counting the items on every
93 request.
94
95 @param group: The current parent group.
96 """
97 sets = group[self.KEY_RULE_SET]
98 group[self.KEY_RULE_SET_COUNT] = len(sets)
99 return sets
100
101
102
104 """
105 Process the regexes by overriding any default ones with API specific regexes
106 and then compile the list of regexes. This also changes the regex key from a
107 string to an integer for easier retrieval later on.
108 """
109
110
111 all_reg = self.branch[self.KEY_REGEXES]
112 default_reg = all_reg[self.KEY_DEFAULT_REGEX_SET]
113
114 if self.API_ID in all_reg:
115
116 for key, reg_item in all_reg[self.API_ID].items():
117 default_reg[key] = reg_item
118
119
120
121
122
123 self.branch[self.KEY_REGEXES] = default_reg
124
126 """
127 This function loops over all the rules in rules_to_run and returns any
128 properties that match. The properties returned can be typed or strings.
129
130 @param rules_to_run: The rules to run against the User-Agent to find the
131 properties.
132 @param user_agent: The User-Agent to find properties for.
133 @param regexes: The list of compiled regexes.
134 @param sought: A set of properites to return values for.
135 """
136
137
138
139 for rule_details in rules_to_run:
140
141 ruleprop_id = rule_details[self.KEY_PROPERTY_MATCHER]
142
143
144
145 if (sought is not None) and (ruleprop_id not in sought):
146 continue
147
148 prop_name = self.tree_provider.property_name_by_id(ruleprop_id)
149
150
151 if self.KEY_PROPERTY_VALUE in rule_details:
152
153
154 prop_val_id = rule_details[self.KEY_PROPERTY_VALUE]
155 value = self.tree_provider.property_value_by_id(prop_val_id)
156
157 self.tree_provider.properties[prop_name[1:]] = Property(value, prop_name[0])
158
159 else:
160
161
162 regex_id = rule_details[self.KEY_RULE_REGEX_ID]
163 patt = regexes[regex_id]
164
165 matches = re.search(patt, user_agent)
166
167 if matches is not None:
168 match_pos = rule_details[self.KEY_REGEX_MATCH_POS]
169 match_res = matches.group(match_pos)
170
171 if match_res != "":
172
173 value = match_res
174 self.tree_provider.properties[prop_name[1:]] = Property(value,
175 prop_name[0])
176
177
178
179
180
181
182
184 """
185 Check list of items that skip rules - these are typically non-mobile boolean
186 properties such as isBrowser, isBot, isCrawler, etc.
187
188 @param id_properties: The results of the tree walk, map of property id to
189 value id
190 @return: TRUE if the UA rules are to be skipped, FALSE if they have to be run
191 """
192 skip_list = self.branch[self.KEY_SKIP_IDS]
193
194 for prop_id in skip_list:
195
196 str_prop_id = str(prop_id)
197
198 if str_prop_id in id_properties:
199 prop_val = self.tree_provider.property_value_by_id(id_properties[str_prop_id])
200 if prop_val is not None and prop_val != 0:
201 return True
202 return False
203
205 """
206 Try and find a set of property extraction rules to run on the User-Agent.
207 This is done in two ways.
208
209 The first way uses properties found from the tree walk to identify the
210 User-Agent type. If there are still multiple UA types then refining regexes
211 can be run.
212
213 If the above approach fails to find a match then fall back to the second way
214 which uses a more brute regex search approach.
215
216 Once the UA type is known the correct set of property extraction rules can
217 be returned.
218
219 @param user_agent: The User-Agent to find properties for.
220 @param id_properties: The results of the tree walk, map of property id to value id.
221 @param rule_groups: All the rule groups that contain the matchers and the rules to
222 run.
223 @param regexes: The list of compiled regexes.
224 @return: a map of rules to run against the User-Agent or None if no rules
225 are found.
226 """
227
228 rules_to_run = []
229
230
231 rules_to_run = self.__find_rules_by_properties(rule_groups, user_agent,
232 id_properties, regexes)
233
234
235
236
237
238
239
240 temp_rules = self.__find_rules_by_regex(rule_groups, user_agent, regexes)
241 if temp_rules is not None and len(temp_rules) > 0:
242 rules_to_run = rules_to_run + temp_rules
243
244 return rules_to_run
245
248 """
249 Try and find User-Agent type and thus the rules to run by using the
250 properties returned from the tree walk. All the properties defined in the
251 property matcher set must match. If a match is found then the rules can be
252 returned.
253
254 @param groups: The rule groups to loop over.
255 @param user_agent: The User-Agent to find properties for.
256 @param regexes: The list of compiled regexes.
257 @return: a dict of rules to run against the User-Agent or None if no
258 rules are found.
259 """
260
261 rules_to_run_a = []
262
263 if groups is None or len(groups) == 0:
264 return rules_to_run_a
265
266 for group in groups:
267
268
269 if group is None or group[self.KEY_PROPERTY_MATCHER] is None:
270 continue
271
272
273
274
275 prop_match = self.__check_properties_match(group[self.KEY_PROPERTY_MATCHER],
276 id_properties)
277
278 if prop_match:
279 rule_set = group[self.KEY_RULE_SET]
280 rule_set_count = group[self.KEY_RULE_SET_COUNT]
281
282
283
284
285 if rule_set_count > 1:
286 rules_to_run = self.__find_rules_to_run_by_regex(user_agent, rule_set,
287 rule_set_count, regexes, self.KEY_REFINE_REGEX_ID)
288 else:
289 rules_set = rule_set[0]
290 rules_to_run = rules_set[self.KEY_RULE_ARR]
291
292 if len(rules_to_run) > 0:
293 rules_to_run_a = rules_to_run_a + rules_to_run
294
295 return rules_to_run_a
296
298 """
299 This functions checks all the properties in the property matcher branch of
300 this rule group. This branch contains a list of properties and their values.
301 All must match for this function to return true.
302
303 In reality the properties and values are indexes to the main property and
304 value arrays.
305
306 @param prop_list: The list of properties to check for matches.
307 @param props_to_values: Dict of property and value ids
308 @return: TRUE if ALL properties match, false otherwise.
309 """
310 prop_match = False
311
312
313 for prop_id, expected_value_id in prop_list.items():
314
315
316 if prop_id in props_to_values:
317
318 tree_value_id = props_to_values[prop_id]
319
320
321 if tree_value_id == expected_value_id:
322 prop_match = True
323 else:
324
325
326
327 return False
328
329 else:
330 return False
331
332 return prop_match
333
335 """
336 Search for the rules to run by checking the User-Agent with a regex. If
337 there is a match the rule list is returned.
338
339 @param groups: The rule groups to loop over.
340 @param user_agent: The User-Agent to find properties for.
341 @param regexes: The list of compiled regexes.
342 @return: a dict of rules to run against the User-Agent or nil if no rules
343 are found.
344 """
345 rules_to_run = []
346
347 if len(groups) > 0:
348
349 i = 0
350 n = len(groups)
351
352 while i < n and len(rules_to_run) > 0:
353
354 group = groups[i]
355 rules_to_run = self.__find_rules_to_run_by_regex(
356 user_agent,
357 group[self.KEY_RULE_SET],
358 group[self.KEY_RULE_SET_COUNT],
359 regexes,
360 self.KEY_SEARCH_REGEX_ID)
361
362 i += 1
363
364 return rules_to_run
365
366
369 """
370 Loop over a set of refining rules to try and determine the User-Agent type
371 and so find the rules to run on it.
372
373 @param user_agent: The User-Agent to find properties for.
374 @param rule_set: The rule_set that contains the search regex id, refine regex id
375 and the magical rules_to_run.
376 @param rule_set_count: The pre-counted items in rule_set.
377 @param regexes: The list of compiled regexes.
378 @param type: The type of rule to run either Refine or Search.
379 @return: a dict of rules to run against the User-Agent or nil if no rules
380 are found.
381 """
382
383
384
385
386
387 for i in range(0, rule_set_count):
388
389 set = rule_set[i]
390
391
392 if type in set:
393
394 regex_id = set[type]
395
396
397 regex = regexes[regex_id]
398
399 if re.search(regex, user_agent):
400 return set[self.KEY_RULE_ARR]
401
402 return []
403