#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  king_phisher/ua_parser.py
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above
#    copyright notice, this list of conditions and the following disclaimer
#    in the documentation and/or other materials provided with the
#    distribution.
#  * Neither the name of the project nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
#  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
#  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
#  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

import collections
import re

from king_phisher.constants import OSArch
from king_phisher.constants import OSFamily

__all__ = ('UserAgent', 'parse_user_agent')
__version__ = '0.2'

USER_AGENT_REGEX_ARCH_PPC = re.compile(
	r'\sPPC\s',
	flags=re.IGNORECASE
)
USER_AGENT_REGEX_ARCH_X86 = re.compile(
	r'(x|(i[3456]))86[^-_]',
	flags=re.IGNORECASE
)
USER_AGENT_REGEX_ARCH_X86_64 = re.compile(
	r'(amd|wow|x(86[-_])?)?64',
	flags=re.IGNORECASE
)
USER_AGENT_REGEX_OS = re.compile(
	r'(android|(bb|blackberry)(\d{4})?|(ipad|iphone)?; cpu (ipad |iphone )?os|linux|mac os x|windows nt|windows phone os)(( |/)([\d\._\-]+)(;|\)| ))?',
	flags=re.IGNORECASE
)
USER_AGENT_REGEX_VERSION = re.compile(r'Version/(([\d\._\-]+)(;|\)| ))')

RE_POS_OS_NAME = 0
RE_POS_OS_VERSION = 7

class UserAgent(collections.namedtuple('UserAgent', ['os_name', 'os_version', 'os_arch'])):
	"""
	A parsed representation of the information available from a browsers user agent
	string. Only the :py:attr:`.os_name` attribute is guaranteed to not be None.

	.. py:attribute:: os_name

		The :py:class:`~.OSFamily` constant of the name of the operating system.

	.. py:attribute:: os_version

		The version of the operating system.

	.. py:attribute:: os_arch

		The :py:class:`~.OSArch` constant of the architecture of the operating system.
	"""
	pass

def parse_user_agent(user_agent):
	"""
	Parse a user agent string and return normalized information regarding the
	operating system.

	:param str user_agent: The user agent to parse.
	:return: A parsed user agent, None is returned if the data can not be processed.
	:rtype: :py:class:`.UserAgent`
	"""
	os_parts = USER_AGENT_REGEX_OS.findall(user_agent)
	if not os_parts:
		return None
	if len(os_parts) > 1:
		os_parts = sorted(os_parts, key=lambda a: len(a[RE_POS_OS_NAME]) + len(a[RE_POS_OS_VERSION]), reverse=True)[0]
	else:
		os_parts = os_parts[0]

	os_version = None
	# os name
	os_name = os_parts[RE_POS_OS_NAME].lower()
	if os_name == 'android':
		os_name = OSFamily.ANDROID
	elif os_name == 'bb' or os_name.startswith('blackberry'):
		os_name = OSFamily.BLACKBERRY
		version_tag = USER_AGENT_REGEX_VERSION.findall(user_agent)
		if version_tag:
			os_version = version_tag[0][1]
	elif 'ipad' in os_name or 'iphone' in os_name:
		os_name = OSFamily.IOS
	elif os_name == 'linux':
		os_name = OSFamily.LINUX
	elif os_name == 'mac os x':
		os_name = OSFamily.OSX
	elif os_name == 'windows nt':
		os_name = OSFamily.WINDOWS
	elif os_name == 'windows phone os':
		os_name = OSFamily.WINDOWS_PHONE
	else:
		return None

	# os version
	os_version = (os_version or '').strip()
	os_version = (os_version or os_parts[RE_POS_OS_VERSION])
	os_version = re.sub(r'[_-]', r'.', os_version) if os_version else None

	# os arch
	os_arch = None
	if USER_AGENT_REGEX_ARCH_X86_64.search(user_agent):
		os_arch = OSArch.X86_64
	elif USER_AGENT_REGEX_ARCH_X86.search(user_agent):
		os_arch = OSArch.X86
	elif USER_AGENT_REGEX_ARCH_PPC.search(user_agent):
		os_arch = OSArch.PPC
	return UserAgent(os_name, os_version, os_arch)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  king_phisher/ua_parser.py
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above
#    copyright notice, this list of conditions and the following disclaimer
#    in the documentation and/or other materials provided with the
#    distribution.
#  * Neither the name of the project nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
#  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
#  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
#  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

import collections
import re

from king_phisher.constants import OSArch
from king_phisher.constants import OSFamily

__all__ = ('UserAgent', 'parse_user_agent')
__version__ = '0.2'

USER_AGENT_REGEX_ARCH_PPC = re.compile(
	r'\sPPC\s',
	flags=re.IGNORECASE
)
USER_AGENT_REGEX_ARCH_X86 = re.compile(
	r'(x|(i[3456]))86[^-_]',
	flags=re.IGNORECASE
)
USER_AGENT_REGEX_ARCH_X86_64 = re.compile(
	r'(amd|wow|x(86[-_])?)?64',
	flags=re.IGNORECASE
)
USER_AGENT_REGEX_OS = re.compile(
	r'(android|(bb|blackberry)(\d{4})?|(ipad|iphone)?; cpu (ipad |iphone )?os|linux|mac os x|windows nt|windows phone os)(( |/)([\d\._\-]+)(;|\)| ))?',
	flags=re.IGNORECASE
)
USER_AGENT_REGEX_VERSION = re.compile(r'Version/(([\d\._\-]+)(;|\)| ))')

RE_POS_OS_NAME = 0
RE_POS_OS_VERSION = 7

class UserAgent(collections.namedtuple('UserAgent', ['os_name', 'os_version', 'os_arch'])):
	"""
	A parsed representation of the information available from a browsers user agent
	string. Only the :py:attr:`.os_name` attribute is guaranteed to not be None.

	.. py:attribute:: os_name

		The :py:class:`~.OSFamily` constant of the name of the operating system.

	.. py:attribute:: os_version

		The version of the operating system.

	.. py:attribute:: os_arch

		The :py:class:`~.OSArch` constant of the architecture of the operating system.
	"""
	pass

def parse_user_agent(user_agent):
	"""
	Parse a user agent string and return normalized information regarding the
	operating system.

	:param str user_agent: The user agent to parse.
	:return: A parsed user agent, None is returned if the data can not be processed.
	:rtype: :py:class:`.UserAgent`
	"""
	os_parts = USER_AGENT_REGEX_OS.findall(user_agent)
	if not os_parts:
		return None
	if len(os_parts) > 1:
		os_parts = sorted(os_parts, key=lambda a: len(a[RE_POS_OS_NAME]) + len(a[RE_POS_OS_VERSION]), reverse=True)[0]
	else:
		os_parts = os_parts[0]

	os_version = None
	# os name
	os_name = os_parts[RE_POS_OS_NAME].lower()
	if os_name == 'android':
		os_name = OSFamily.ANDROID
	elif os_name == 'bb' or os_name.startswith('blackberry'):
		os_name = OSFamily.BLACKBERRY
		version_tag = USER_AGENT_REGEX_VERSION.findall(user_agent)
		if version_tag:
			os_version = version_tag[0][1]
	elif 'ipad' in os_name or 'iphone' in os_name:
		os_name = OSFamily.IOS
	elif os_name == 'linux':
		os_name = OSFamily.LINUX
	elif os_name == 'mac os x':
		os_name = OSFamily.OSX
	elif os_name == 'windows nt':
		os_name = OSFamily.WINDOWS
	elif os_name == 'windows phone os':
		os_name = OSFamily.WINDOWS_PHONE
	else:
		return None

	# os version
	os_version = (os_version or '').strip()
	os_version = (os_version or os_parts[RE_POS_OS_VERSION])
	os_version = re.sub(r'[_-]', r'.', os_version) if os_version else None

	# os arch
	os_arch = None
	if USER_AGENT_REGEX_ARCH_X86_64.search(user_agent):
		os_arch = OSArch.X86_64
	elif USER_AGENT_REGEX_ARCH_X86.search(user_agent):
		os_arch = OSArch.X86
	elif USER_AGENT_REGEX_ARCH_PPC.search(user_agent):
		os_arch = OSArch.PPC
	return UserAgent(os_name, os_version, os_arch)

#!/usr/bin/env python # -*- coding: utf-8 -*- # #  king_phisher/ua_parser.py # #  Redistribution and use in source and binary forms, with or without #  modification, are permitted provided that the following conditions are #  met: # #  * Redistributions of source code must retain the above copyright #    notice, this list of conditions and the following disclaimer. #  * Redistributions in binary form must reproduce the above #    copyright notice, this list of conditions and the following disclaimer #    in the documentation and/or other materials provided with the #    distribution. #  * Neither the name of the project nor the names of its #    contributors may be used to endorse or promote products derived from #    this software without specific prior written permission. # #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS #  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT #  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR #  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT #  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, #  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT #  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, #  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY #  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT #  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # A parsed representation of the information available from a browsers user agent 	string. Only the :py:attr:`.os_name` attribute is guaranteed to not be None.  	.. py:attribute:: os_name  		The :py:class:`~.OSFamily` constant of the name of the operating system.  	.. py:attribute:: os_version  		The version of the operating system.  	.. py:attribute:: os_arch  		The :py:class:`~.OSArch` constant of the architecture of the operating system. Parse a user agent string and return normalized information regarding the 	operating system.  	:param str user_agent: The user agent to parse. 	:return: A parsed user agent, None is returned if the data can not be processed. 	:rtype: :py:class:`.UserAgent` # os name # os version # os arch