Example #1
0
 def setUp(self):
     self.Extra = HDExtra()
Example #2
0
class HDExtraTests(unittest.TestCase):
    def setUp(self):
        self.Extra = HDExtra()

    def test_comparePlatformVersionsA(self):
        result = self.Extra.comparePlatformVersions('9.0.1', '9.1')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsB(self):
        result = self.Extra.comparePlatformVersions('9.0.1', '9.0.1')
        self.assertEqual(result, 0)

    def test_comparePlatformVersionsC(self):
        result = self.Extra.comparePlatformVersions('9.1', '9.0.1')
        self.assertGreaterEqual(result, result)

    def test_comparePlatformVersionsD(self):
        result = self.Extra.comparePlatformVersions('4.2.1', '9.1')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsD2(self):
        result = self.Extra.comparePlatformVersions('4.2.1', '9')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsD3(self):
        result = self.Extra.comparePlatformVersions('4', '9.1.1')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsE(self):
        result = self.Extra.comparePlatformVersions('4.2.1', '4.2.2')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsF(self):
        result = self.Extra.comparePlatformVersions('4.2.1', '4.2.12')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsG(self):
        result = self.Extra.comparePlatformVersions('4.1.1', '4.2.1')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsH(self):
        result = self.Extra.comparePlatformVersions('4.1', '4.1.1')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsI(self):
        result = self.Extra.comparePlatformVersions('4.0.21', '40.21')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsJ(self):
        result = self.Extra.comparePlatformVersions('4.0.21', '10.00.1')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsK(self):
        result = self.Extra.comparePlatformVersions('4.0.21', '10.00.1')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsK2(self):
        result = self.Extra.comparePlatformVersions('10.00.1', '4')
        self.assertGreaterEqual(result, 1)

    def test_comparePlatformVersionsL(self):
        result = self.Extra.comparePlatformVersions('Q7.1', 'Q7.2')
        self.assertLessEqual(result, -1)

    def test_comparePlatformVersionsM(self):
        result = self.Extra.comparePlatformVersions('Q5SK', 'Q7SK')
        self.assertLessEqual(result, -1)
Example #3
0
	def __init__(self, config = None):
		self._Extra = HDExtra()
		self._Store = HDStore()
		if config is not None:
			self.setConfig(config)
Example #4
0
class HDDevice(HDBase):
	DETECTIONV4_STANDARD = '0'
	DETECTIONV4_GENERIC = '1'

	_device = None
	_platform = None
	_browser = None
	_app = None
	_ratingResult = None
	_Extra = None
	_Store = None
	config = {}
	
	def __init__(self, config = None):
		self._Extra = HDExtra()
		self._Store = HDStore()
		if config is not None:
			self.setConfig(config)

	def setConfig(self, config):
		for key in config['config']:
			self.config[key] = config['config'][key]

		self._Store.setConfig(config)
		self._Extra.setConfig(config)
		
	def localVendors(self):
		self.reply = {}
		self.reply['vendor'] = []
		self.setError(301, 'Nothing Found');

		deviceList = self._fetchDevices()
		if deviceList is None:
			return self.reply

		replySet = set()
		for device in deviceList:
			replySet.add(device['Device']['hd_specs']['general_vendor'])

		tmpList = list(replySet)
		tmpList.sort()
		self.reply['vendor'] = tmpList
		self.setError(0, 'OK')
		return self.reply

	def localModels(self, vendor):
		self.reply = {}
		self.reply['model'] = []
		self.setError(301, 'Nothing Found');
		
		deviceList = self._fetchDevices()
		if deviceList is None:
			return self.reply

		vendor = vendor.lower()
		replySet = set()
		for device in deviceList:
			deviceVendor = device['Device']['hd_specs']['general_vendor'].lower()
			if deviceVendor == vendor:
				replySet.add(device['Device']['hd_specs']['general_model'])
			searchKey = vendor + " "
			for alias in device['Device']['hd_specs']['general_aliases']:
				alias = alias.lower()
				if alias.find(searchKey) is 0:
					replySet.add(alias.replace(searchKey, ''))

		tmpList = list(replySet)
		tmpList.sort()
		self.reply['model'] = tmpList
		self.setError(0, 'OK')
		return self.reply

	def localView(self, vendor, model):
		self.reply = {}
		self.reply['device'] = {}
		self.setError(301, 'Nothing Found');

		deviceList = self._fetchDevices()
		if deviceList is None:
			return self.reply

		vendor = vendor.lower()
		model = model.lower()
		for device in deviceList:
			if device['Device']['hd_specs']['general_vendor'].lower() == vendor and device['Device']['hd_specs']['general_model'].lower() == model:
				self.reply['device'] = device['Device']['hd_specs']
				self.setError(0, 'OK')
		return self.reply

	def localWhatHas(self, key, value):
		self.reply = {}
		self.reply['devices'] = []
		self.setError(301, 'Nothing Found');
		
		deviceList = self._fetchDevices()
		if deviceList is None:
			return self.reply

		value = value.lower()
		for device in deviceList:
			if device['Device']['hd_specs'][key] is None:
				continue

			match = None
			if isinstance(device['Device']['hd_specs'][key], list):
				for item in device['Device']['hd_specs'][key]:
					if value.find(item.lower()) > -1:
						match = True
			elif value.find(device['Device']['hd_specs'][key].lower()) > -1:
				match = True

			if match == True:
				tmp = {}
				tmp['_id'] = device['Device']['_id']
				tmp['general_vendor'] = device['Device']['hd_specs']['general_vendor']
				tmp['general_model'] = device['Device']['hd_specs']['general_model']
				self.reply['devices'].append(tmp)

		self.setError(0, 'OK')
		return self.reply
	
	def _fetchDevices(self):
		deviceList = self._Store.fetchDevices()
		if deviceList is None:
			return self.setError(299, "Error : fetchDevices cannot read files from store.")
		return deviceList

	def localDetect(self, headers):
		newHeaders = {}
		hardwareInfo = ''

		for k in headers:
			newHeaders[k.lower()] = headers[k]

		if 'x-local-hardwareinfo' in newHeaders:
			hardwareInfo = newHeaders['x-local-hardwareinfo']
			del newHeaders['x-local-hardwareinfo']

		if self.hasBiKeys(newHeaders) is not None:
			return self.v4MatchBuildInfo(newHeaders)
		return self.v4MatchHttpHeaders(newHeaders, hardwareInfo)


	def v4MatchBuildInfo(self, headers):
		self._device = None
		self._platform = None
		self._browser = None
		self._app = None
		self._detectedRuleKey = None
		self._ratingResult = None
		self.reply = {}
		self.setError(301, 'Not Found');

		if headers is None:
			return self.reply

		self._headers = headers

		self._device = self.v4MatchBiHelper(headers, 'device')
		if self._device is None:
			return None

		self._platform = self.v4MatchBiHelper(headers, 'platform')
		if self._platform is not None:
			self.specsOverlay('platform', self._device, self._platform)

		self.reply['hd_specs'] = self._device['Device']['hd_specs']
		self.setError(0, 'OK')
		return self.reply

	def v4MatchBiHelper(self, headers, category):
		confBiKeys = self._detectionConfig[category + '-bi-order']
		if confBiKeys is None or headers is None:
			return None

		hints = []

		for platform in confBiKeys:
			value = ''
			for items in confBiKeys[platform]:
				checking = True
				for item in items:
					if item in headers:
						value = headers[item] if value == '' else value + '|' + headers[item]
					else:
						checking = False
						break
				if checking == True:
					value.strip("| \t\n\r\0\x0B")
					hints.append(value)
					subtree = self.DETECTIONV4_STANDARD if category == 'device' else category
					_id = self.getMatch('buildinfo', value, subtree, 'buildinfo', category)
					if _id is not None:
						return self.findById(_id) if category == 'device' else self._Extra.findById(_id)

		# If we get this far without a result then try a generic match
		platform = self.hasBiKeys(headers)
		if platform is not None:
			tryList = []
			tryList.append("generic|" + platform)
			tryList.append(platform + "|generic")
			for attempt in tryList:
				subtree = self.DETECTIONV4_GENERIC if category == 'device' else category
				_id = self.getMatch('buildinfo', value, subtree, 'buildinfo', category)
				if _id is not None:
					return self.findById(_id) if category == 'device' else self._Extra.findById(_id)

		return None

	def v4MatchHttpHeaders(self, headers, hardwareInfo):
		self._device = {}
		self._platform = {}
		self._browser = {}
		self._app = {}
		self._language = {}
		self._detectedRuleKey = {}
		self._ratingResult = {}
		self.reply = {}
		self.setError(301, 'Not Found');
		hwProps = {}
		deviceHeaders = {}
		extraHeaders = {}
		
		if headers is None:
			return self.reply

		if 'ip' in headers:
			del headers['ip']
		if 'host' in headers:
			del headers['host']

		# Sanitize headers & cleanup language (you filthy animal) :)
		for k in headers:
			v = headers[k].lower()
			if k == 'accept-language' or k == 'content-language':
				tmp = re.split("[,;]", v)
				k = 'language'
				if tmp is not None and len(tmp) > 0:
					v = tmp[0]
				else:
					continue
			deviceHeaders[k] = self.cleanStr(v)
			extraHeaders[k] = self.extraCleanStr(v)

		self._device = self.matchDevice(deviceHeaders)
		if self._device is None:
			return self.reply

		if hardwareInfo != '':
			hwProps = self.infoStringToArray(hardwareInfo)

		# Stop on detect set - Tidy up and return
		if self._device['Device']['hd_ops']['stop_on_detect'] == '1':
			if self._device['Device']['hd_ops']['overlay_result_specs'] == '1':
				self.hardwareInfoOverlay(self._device, hwProps)
			self.reply['hd_specs'] = self._device['Device']['hd_specs']
			self.setError(0, 'OK')
			return self.reply

		# Get extra info
		self._platform = self._Extra.matchExtra('platform', extraHeaders)
		self._browser = self._Extra.matchExtra('browser', extraHeaders)
		self._app = self._Extra.matchExtra('app', extraHeaders)
		self._language = self._Extra.matchLanguage('language', extraHeaders)

		# Find out if there is any contention on the detected rule.
		deviceList = self.getHighAccuracyCandidates()
		if deviceList is not None:

			# Resolve contention with OS check
			self._Extra._data = self._platform
			pass1List = []
			for _id in deviceList:
				tryDevice = self.findById(_id)
				if self._Extra.verifyPlatform(tryDevice['Device']['hd_specs']) is True:
					pass1List.append(_id)

			# Contention still not resolved .. check hardware
			if len(pass1List) >= 2 and hwProps is not None and len(hwProps) > 0:
				# Score the list based on hardware
				ratedResult = [];
				for _id in pass1List:
					tmp = self.findRating(_id, hwProps)
					if tmp is not None:
						ratedResult.append(tmp);

				# Find winning device by picking the one with the highest score.
				# If scores are even choose the one with the lowest distance
				winningDevice = None
				for tmpDevice in ratedResult:
					if winningDevice == None:
						winningDevice = tmpDevice
					else:
						if tmpDevice['score'] > winningDevice['score'] or (tmpDevice['score'] == winningDevice['score'] and tmpDevice['distance'] < winningDevice['distance']):
							winningDevice = tmpDevice;

				self._device = self.findById(winningDevice['_id'])

		# Overlay specs
		if self._platform is not None and 'Extra' in self._platform:
			self.specsOverlay('platform', self._device, self._platform['Extra'])
		if self._browser is not None and 'Extra' in self._browser:
			self.specsOverlay('browser', self._device, self._browser['Extra'])
		if self._app is not None and 'Extra' in self._app:
			self.specsOverlay('app', self._device, self._app['Extra'])
		if self._language is not None and 'Extra' in self._language:
			self.specsOverlay('language', self._device, self._language['Extra'])

		# Overlay hardware info result if required
		if (self._device['Device']['hd_ops']['overlay_result_specs'] == 1 or self._device['Device']['hd_ops']['overlay_result_specs'] == '1') and hardwareInfo != '':
			self.hardwareInfoOverlay(self._device, hwProps)

		self.reply['hd_specs'] = self._device['Device']['hd_specs']
		self.setError(0, 'OK')
		return self.reply

	def findRating(self, deviceId, props):
		""" Rates a device as compared to a set of hardware properties

		deviceId string - A device ID
		props dict - A dictionary of properties
		return dictionary
		"""
		device = self.findById(deviceId)
		if device is None:
			return None

		specs = device['Device']['hd_specs']
		total = 0;
		result = {}

		# Display Resolution - Worth 40 points if correct
		if 'display_x' in props and 'display_y' in props:
			total += 40
			if int(specs['display_x']) == int(props['display_x']) and int(specs['display_y']) == int(props['display_y']):
				result['resolution'] = 40
			elif int(specs['display_x']) == int(props['display_y']) and int(specs['display_y']) == int(props['display_x']):
				result['resolution'] = 40
			elif float(specs['display_pixel_ratio']) > 1.0:
				# The resolution can be scaled by the pixel ratio for some devices
				adjX = int(int(props['display_x']) * float(specs['display_pixel_ratio']))
				adjY = int(int(props['display_y']) * float(specs['display_pixel_ratio']))
				if int(specs['display_x']) == adjX and int(specs['display_y']) == adjY:
					result['resolution'] = 40
				elif int(specs['display_x']) == adjY and int(specs['display_y']) == adjX:
					result['resolution'] = 40

		# Display pixel ratio - Also worth 40 points

		if 'display_pixel_ratio' in props:
			total += 40;
			# Note : display_pixel_ratio will be a string stored as 1.33 or 1.5 or 2, perhaps 2.0 ..
			if specs['display_pixel_ratio'] == str(round(props['display_pixel_ratio']/100, 2)):
				result['display_pixel_ratio'] = 40;

		# Benchmark - 10 points - Enough to tie break but not enough to overrule display or pixel ratio.
		if 'benchmark' in props:
			total += 10;
			if 'benchmark_min' in specs and 'benchmark_max' in specs:
				if int(props['benchmark']) >= int(specs['benchmark_min']) and int(props['benchmark']) <= int(specs['benchmark_max']):
					# Inside range
					result['benchmark'] = 10
				else:
					# Outside range
					result['benchmark'] = 0

		result['score'] = 0 if total == 0 else int(sum(result.values()))
		result['possible'] = total;

		# Distance from mean used in tie breaking situations if two devices have the same score.
		result['distance'] = 100000
		if 'benchmark_min' in specs and 'benchmark_max' in specs and 'benchmark' in props:
			result['distance'] = int(abs(((specs['benchmark_min'] + specs['benchmark_max'])/2) - props['benchmark']))

		result['_id'] = deviceId
		return result

	def specsOverlay(self, category, device, specs):
		"""Overlays specs onto a device
		
		category string - 'platform' | 'browser' | 'app' | 'language
		device object - A device dictionary
		"""
		if category == "platform":
			if specs['hd_specs']['general_platform'] != "":
				device['Device']['hd_specs']['general_platform'] = specs['hd_specs']['general_platform'];
				device['Device']['hd_specs']['general_platform_version'] = specs['hd_specs']['general_platform_version'];
		elif category == 'browser':
			if specs['hd_specs']['general_browser'] != "":
				device['Device']['hd_specs']['general_browser'] = specs['hd_specs']['general_browser'];
				device['Device']['hd_specs']['general_browser_version'] = specs['hd_specs']['general_browser_version'];
		elif category == 'app':
			if specs['hd_specs']['general_app'] != "":
				device['Device']['hd_specs']['general_app'] = specs['hd_specs']['general_app'];
				device['Device']['hd_specs']['general_app_version'] = specs['hd_specs']['general_app_version'];
				device['Device']['hd_specs']['general_app_category'] = specs['hd_specs']['general_app_category'];
		elif category == 'language':
			if specs['hd_specs']['general_language'] != "":
				device['Device']['hd_specs']['general_language'] = specs['hd_specs']['general_language'];
				device['Device']['hd_specs']['general_language_full'] = specs['hd_specs']['general_language_full'];
		#return device ??


	def infoStringToArray(self, hardwareInfo):
		"""
			Takes a string of onDeviceInformation and turns it into something that can be used for high accuracy checking.
			Strings a usually generated from cookies, but may also be supplied in headers.
			The format is $w:$h:$r:$b where w is the display width, h is the display height, r is the pixel ratio and b is the benchmark.
				display_x, display_y, display_pixel_ratio, general_benchmark
				
			@param string $hardwareInfo String of light weight device property information, separated by ':'
			@return dictionary partial specs array of information we can use to improve detection accuracy
		"""
		# Remove the header or cookie name from the string 'x-specs1a='
		if hardwareInfo.find('=') > -1:
			cookieName, hardwareInfo = hardwareInfo.split('=')

		reply = {}
		info = hardwareInfo.split(":")
		if len(info) != 4:
			return reply
		reply['display_x'] = int(info[0])
		reply['display_y'] = int(info[1])
		reply['display_pixel_ratio'] = int(info[2])
		reply['benchmark'] = int(info[3])
		return reply;

	def hardwareInfoOverlay(self, device, info):
		"""Overlays hardware info onto a device - Used in generic replys
		device dictionary
		info dictionary
		"""
		if 'display_x' in info and info['display_x'] != 0:
			device['Device']['hd_specs']['display_x'] = info['display_x']
		if 'display_y' in info and info['display_y'] != 0:
			device['Device']['hd_specs']['display_y'] = info['display_y']
		if 'display_pixel_ratio' in info and info['display_pixel_ratio'] != 0:
			device['Device']['hd_specs']['display_pixel_ratio'] = str(round(float(info['display_pixel_ratio'] / 100), 2))
		# return device ??
		
	def matchDevice(self, headers):
		"""
		Device matching

		Plan of attack :
			1) Look for opera headers first - as they're definitive
			2) Try profile match - only devices which have unique profiles will match.
			3) Try user-agent match
			4) Try other x-headers
			5) Try all remaining headers
		"""
		# May need to assign headers to an interneal variable - not sure if its pass by ref or value ...
		# Remember the agent for generic matching later.
		agent = ""

		# Opera mini sometimes puts the vendor # model in the header - nice! ... sometimes it puts ? # ? in as well
		if 'x-operamini-phone' in headers and headers['x-operamini-phone'] != "? # ?":
			_id = self.getMatch('x-operamini-phone', headers['x-operamini-phone'], self.DETECTIONV4_STANDARD, 'x-operamini-phone', 'device')
			if _id is not None:
				return self.findById(_id)
			agent = headers['x-operamini-phone']
			del headers['x-operamini-phone']

		# Profile header matching
		if 'profile' in headers:
			_id = self.getMatch('profile', headers['profile'], self.DETECTIONV4_STANDARD, 'profile', 'device')
			if _id is not None:
				return self.findById(_id)
			del headers['profile']

		# Profile header matching - native header name
		if 'x-wap-profile' in headers:
			_id = self.getMatch('profile', headers['x-wap-profile'], self.DETECTIONV4_STANDARD, 'x-wap-profile', 'device')
			if _id is not None:
				return self.findById(_id)
			del headers['x-wap-profile']


		# Match nominated headers ahead of x- headers, but do check x- headers
		order = self._detectionConfig['device-ua-order']
		for k in headers:
			if k not in order and k.startswith("x-"):
				order.append(k)

		for item in order:
			if item in headers:
				self.log("Trying user-agent match on header " + item)
				_id = self.getMatch('user-agent', headers[item], self.DETECTIONV4_STANDARD, item, 'device')
				if _id is not None:
					return self.findById(_id)

		# Generic matching - Match of last resort
		self.log('Trying Generic Match')

		_id = None
		if 'x-operamini-phone-ua' in headers:
			_id = self.getMatch('agent', headers['x-operamini-phone-ua'], self.DETECTIONV4_GENERIC, 'agent', 'device')
		if 'agent' in headers and _id is None:
			_id = self.getMatch('user-agent', headers['agent'], self.DETECTIONV4_GENERIC, 'agent', 'device')
		if 'user-agent' in headers and _id is None:
			_id = self.getMatch('user-agent', headers['user-agent'], self.DETECTIONV4_GENERIC, 'agent', 'device')

		if _id is not None:
			return self.findById(_id)

		return None

	def findById(self, _id):
		return self._Store.read("Device_" + _id)

	def getHighAccuracyCandidates(self):
		""" Determines if High Accuracy checks are available on the device which was just detected """
		branch = self.getBranch('hachecks')
		ruleKey = self._detectedRuleKey['device'] if 'device' in self._detectedRuleKey else None;
		if ruleKey in branch:
			return branch[ruleKey]
		return None

	def isHelperUseful(self, headers):
		""" Determines of hd4Helper would prove more accurate detection results """
		if headers is None:
			return None

		if self.localDetect(headers) is None:
			return None

		if self.getHighAccuracyCandidates() is None:
			return None

		return True
	def setUp(self):
		self.Extra = HDExtra()
class HDExtraTests(unittest.TestCase):

	def setUp(self):
		self.Extra = HDExtra()

	def test_comparePlatformVersionsA(self):
		result = self.Extra.comparePlatformVersions('9.0.1', '9.1')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsB(self):
		result = self.Extra.comparePlatformVersions('9.0.1', '9.0.1')
		self.assertEqual(result, 0)

	def test_comparePlatformVersionsC(self):
		result = self.Extra.comparePlatformVersions('9.1', '9.0.1')
		self.assertGreaterEqual(result, result)

	def test_comparePlatformVersionsD(self):
		result = self.Extra.comparePlatformVersions('4.2.1', '9.1')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsD2(self):
		result = self.Extra.comparePlatformVersions('4.2.1', '9')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsD3(self):
		result = self.Extra.comparePlatformVersions('4', '9.1.1')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsE(self):
		result = self.Extra.comparePlatformVersions('4.2.1', '4.2.2')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsF(self):
		result = self.Extra.comparePlatformVersions('4.2.1', '4.2.12')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsG(self):
		result = self.Extra.comparePlatformVersions('4.1.1', '4.2.1')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsH(self):
		result = self.Extra.comparePlatformVersions('4.1', '4.1.1')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsI(self):
		result = self.Extra.comparePlatformVersions('4.0.21', '40.21')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsJ(self):
		result = self.Extra.comparePlatformVersions('4.0.21', '10.00.1')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsK(self):
		result = self.Extra.comparePlatformVersions('4.0.21', '10.00.1')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsK2(self):
		result = self.Extra.comparePlatformVersions('10.00.1', '4')
		self.assertGreaterEqual(result, 1)

	def test_comparePlatformVersionsL(self):
		result = self.Extra.comparePlatformVersions('Q7.1', 'Q7.2')
		self.assertLessEqual(result, -1)

	def test_comparePlatformVersionsM(self):
		result = self.Extra.comparePlatformVersions('Q5SK', 'Q7SK')
		self.assertLessEqual(result, -1)