<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Unofficial python-api for the Speedport Hybrid CPE"""

import requests
import json
import hashlib
import re
# from pbkdf2 import PBKDF2 # Moved to SpeedportHybridApi._getDerivedKey()


class SpeedportHybridApi(object):
	"""An Api for the Speedport Hybrid's web interface"""

	def __init__(self, host=None, session=None):
		super(SpeedportHybridApi, self).__init__()

		# Initialize default values
		self.Host = 'speedport.ip'
		self.RequestParams = { 
			'timeout': 5, # Time out after 5s
		}
		self.Session = None

		if host is not None:
			self.Host = host

		if session is not None:
			# Session overwrites host
			if host is not None and host != session.Host:
				raise SpeedportHybridApi.ApiException('Cannot instantiate Api with diverging host and session configuration')

			session.apply(self)

	class ApiException(Exception):
		"""The baseclass for exceptions raised by SpeedportHybridApi"""

		def __init__(self, message, cause = None):
			super(SpeedportHybridApi.ApiException, self).__init__(message)

			self.Cause = cause

		def __repr__(self):
			s = str(super(SpeedportHybridApi.ApiException, self))

			return '%s' % s if self.Cause is None else '%s - Cause: %s' % (s, str(self.Cause))

		def __str__(self):
			if self.Cause is not None and self.Cause.hasattr('message'):
				return '%s > %s' % (self.message, self.Cause.message)

			return self.message

	class RequestException(ApiException):
		"""An exception to be risen if case of request-related problems"""
		def __init__(self, message, cause):
			super(SpeedportHybridApi.RequestException, self).__init__(message, cause)

	class SessionException(ApiException):
		"""An exception for session-related problems"""
		def __init__(self, message):
			super(SpeedportHybridApi.SessionException, self).__init__(message)

	class MissingSessionException(SessionException):
		"""An exception in case of a missing session"""
		def __init__(self):
			super(SpeedportHybridApi.MissingSessionException, self).__init__('The session has not been set and thus is missing')

	class JsonParserException(ApiException):
		"""An exception in case of parsing an invalid JSON string"""
		JsonString = None

		def __init__(self, jsonString, cause):
			super(SpeedportHybridApi.JsonParserException, self).__init__('Failed parsing JSON string', cause)
			self.JsonString = jsonString

	class ApiSession(object):
		"""A container for SpeedportHybridApi sessions"""

		def __init__(self, host, challenge, dk, sid):
			super(SpeedportHybridApi.ApiSession, self).__init__()

			self.Host = host
			self.Challenge = challenge
			self.DerivedKey = dk
			self.ID = sid

		def apply(self, api):
			api.Host = self.Host
			api.Session = self

		def __str__(self):
			return 'Session at %s: SessionID_R3=%s; challengev=%s; derivedk=%s' % (self.Host, self.ID, self.Challenge, self.DerivedKey)

		def __repr(self):
			return '<%s>' % str(self)

		def getNewApi(self):
			"""Create a new SpeedportHybridApi instance based on this session"""
			return SpeedportHybridApi(session=self)

		def asList(self):
			"""Return session parameters in a list"""
			return [self.Host, self.Challenge, self.DerivedKey, self.ID]

		def isValid(self, api=None):
			"""Determine if the current session is still valid

			* NOT IMPLEMENTED YET *"""
			api = api or self.getNewApi()

			raise NotImplementedError()

	class JsonVar(object):
		"""A container for json-variables returned by the router-api"""
		def __init__(self, jsonVarObject):
			super(SpeedportHybridApi.JsonVar, self).__init__()

			self.ID = jsonVarObject['varid']
			self.Value = jsonVarObject['varvalue']
			self.Type = jsonVarObject['vartype']
			#self.Source = jsonVarObject # Store reference to jsonVarObject

		def __str__(self):
			return 'JsonVar "%s" [%s]: (%d) %s%s' % (self.ID, self.Type, len(self.Value), self.Value[:20].__repr__(), ('..' if len(self.Value) > 20 else ''))

		def __repr__(self):
			return '<%s>' % str(self)

	def hasSession(self):
		"""Checks if there is a session-object associated"""
		return self.Session is not None

	def _makeUri(self, path):
		"""Format a request uri based on current Host and requested path"""
		return 'http://%s/%s' % (self.Host, path)

	def loadJson(self, uri, expectCode=200, fix=True):
		"""Load JSON via a GET-request from given path. The http-status-code expectCode is asserted. By default invalid json responses are fixed."""
		
		# Perform request
		try:
			r = self._getRequest(uri, expectCode=expectCode)
		except Exception as e:
			raise SpeedportHybridApi.RequestException('failed to perform json-request', e)

		# Expect JSON response, not HTML
		# Todo: Implement generic expectation-handler
		assert r.headers['content-type'] != 'text/html'

		# Parse response
		try: 
			res = self._parseJsonResponse(r.text, fix=fix)
		except Exception as e:
			raise SpeedportHybridApi.ApiException('parsing json response failed', e)

		return res, r # res is dict of JsonVar, r is request object

	def _performRequest(self, method, uri, params, expectCode, noSession):
		"""Perform a http request with specified characteristics"""
		uri = self._makeUri(uri)

		# Check is session used and existing
		if not (noSession or self.hasSession()):
			raise SpeedportHybridApi.MissingSessionException()

		# Set cookie-header if needed
		if 'cookies' not in params and not noSession:
			params['cookies'] = {'challengev': self.Session.Challenge, 'derivedk': self.Session.DerivedKey, 'SessionID_R3': self.Session.ID}

		r = method(uri, **params)

		# Assert http status code expectation (200 OK default)
		if expectCode is not None:
			assert r.status_code == expectCode # Expect proper status code in response

		return r

	def _getRequest(self, uri, expectCode=200, noSession=False, ownParams=None):
		"""Perform http-get request with specified characteristics"""
		params = ownParams or self.RequestParams.copy()

		return self._performRequest(requests.get, uri, params, expectCode, noSession)

	def _postRequest(self, uri, data, expectCode=200, noSession=False, ownParams=None):
		"""Perform http-post request with specified characteristics and data"""
		params = (ownParams or self.RequestParams).copy()

		params['data'] = data

		return self._performRequest(requests.post, uri, params, expectCode, noSession)

	def _parseJsonResponse(self, jsonString, isJsonVars=False, fix=True):
		"""Parse JSON-String. Invalid json is sanified per default. If requested the json is parsed as JsonVar-dict."""
		s = jsonString

		# Fix invalid json
		if fix:
			# Single Quotes to Double Quotes
			s = s.replace("'", '"')

			# Remove redundant trailing ',' 
			s = re.sub(r',(?=[\n\s]*[\]\}])|,(?=[\n\s]*$)', '', s)

			# Embed in an array
			if (re.match(r'^[\n\s]*\[', s) is None) and (re.match(r'\][\n\s]*$', s) is None):
				s = '[%s]' % s

		try:
			# Parse JSON String
			o = json.loads(s)

			# Parse vars if needed
			if isJsonVars:
				vx  = [SpeedportHybridApi.JsonVar(j) for j in o]<commit_msg>Improvements to loadJson-method, handling json mime types<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Unofficial python-api for the Speedport Hybrid CPE"""

import requests
import json
import hashlib
import re
# from pbkdf2 import PBKDF2 # Moved to SpeedportHybridApi._getDerivedKey()


class SpeedportHybridApi(object):
	"""An Api for the Speedport Hybrid's web interface"""

	def __init__(self, host=None, session=None):
		super(SpeedportHybridApi, self).__init__()

		# Initialize default values
		self.Host = 'speedport.ip'
		self.RequestParams = { 
			'timeout': 5, # Time out after 5s
		}
		self.Session = None

		if host is not None:
			self.Host = host

		if session is not None:
			# Session overwrites host
			if host is not None and host != session.Host:
				raise SpeedportHybridApi.ApiException('Cannot instantiate Api with diverging host and session configuration')

			session.apply(self)

	class ApiException(Exception):
		"""The baseclass for exceptions raised by SpeedportHybridApi"""

		def __init__(self, message, cause = None):
			super(SpeedportHybridApi.ApiException, self).__init__(message)

			self.Cause = cause

		def __repr__(self):
			s = str(super(SpeedportHybridApi.ApiException, self))

			return '%s' % s if self.Cause is None else '%s - Cause: %s' % (s, str(self.Cause))

		def __str__(self):
			if self.Cause is not None and self.Cause.hasattr('message'):
				return '%s > %s' % (self.message, self.Cause.message)

			return self.message

	class RequestException(ApiException):
		"""An exception to be risen if case of request-related problems"""
		def __init__(self, message, cause):
			super(SpeedportHybridApi.RequestException, self).__init__(message, cause)

	class SessionException(ApiException):
		"""An exception for session-related problems"""
		def __init__(self, message):
			super(SpeedportHybridApi.SessionException, self).__init__(message)

	class MissingSessionException(SessionException):
		"""An exception in case of a missing session"""
		def __init__(self):
			super(SpeedportHybridApi.MissingSessionException, self).__init__('The session has not been set and thus is missing')

	class JsonParserException(ApiException):
		"""An exception in case of parsing an invalid JSON string"""
		JsonString = None

		def __init__(self, jsonString, cause):
			super(SpeedportHybridApi.JsonParserException, self).__init__('Failed parsing JSON string', cause)
			self.JsonString = jsonString

	class ApiSession(object):
		"""A container for SpeedportHybridApi sessions"""

		def __init__(self, host, challenge, dk, sid):
			super(SpeedportHybridApi.ApiSession, self).__init__()

			self.Host = host
			self.Challenge = challenge
			self.DerivedKey = dk
			self.ID = sid

		def apply(self, api):
			api.Host = self.Host
			api.Session = self

		def __str__(self):
			return 'Session at %s: SessionID_R3=%s; challengev=%s; derivedk=%s' % (self.Host, self.ID, self.Challenge, self.DerivedKey)

		def __repr(self):
			return '<%s>' % str(self)

		def getNewApi(self):
			"""Create a new SpeedportHybridApi instance based on this session"""
			return SpeedportHybridApi(session=self)

		def asList(self):
			"""Return session parameters in a list"""
			return [self.Host, self.Challenge, self.DerivedKey, self.ID]

		def isValid(self, api=None):
			"""Determine if the current session is still valid

			* NOT IMPLEMENTED YET *"""
			api = api or self.getNewApi()

			raise NotImplementedError()

	class JsonVar(object):
		"""A container for json-variables returned by the router-api"""
		def __init__(self, jsonVarObject):
			super(SpeedportHybridApi.JsonVar, self).__init__()

			self.ID = jsonVarObject['varid']
			self.Value = jsonVarObject['varvalue']
			self.Type = jsonVarObject['vartype']
			#self.Source = jsonVarObject # Store reference to jsonVarObject

		def __str__(self):
			return self.Value

		def __repr__(self):
			return '[(JsonVar) "%s" [%s]: (%d) %s%s]' % (self.ID, self.Type, len(self.Value), self.Value[:20].__repr__(), ('..' if len(self.Value) > 20 else ''))

	def hasSession(self):
		"""Checks if there is a session-object associated"""
		return self.Session is not None

	def _makeUrl(self, path, protocol='http'):
		"""Format a request uri based on current Host and requested path to form a url"""
		return '%s://%s/%s' % (protocol, self.Host, path)
	
	_jsonMimeTypes = ['application/javascript', 'application/json']

	def loadJson(self, uri, jsonvar=False, expectCode=200, fix=True, expectMimeType=_jsonMimeTypes):
		"""Load JSON via a GET-request from given path. The http-status-code expectCode is asserted. By default invalid json responses are fixed."""

		params = None
		headers = None

		if expectMimeType is not None:
			headers = {'Accept': ','.join(expectMimeType)}

		if headers is not None:
			params = {'headers': headers}

		# Perform request
		try:
			r = self._getRequest(uri, expectCode=expectCode)
		except Exception as e:
			raise SpeedportHybridApi.RequestException('failed to perform json-request', e)

		# Expect JSON response, not HTML
		# Todo: Implement generic expectation-handler
		if expectMimeType is not None:
			assert r.headers['content-type'] in expectMimeType
			#assert r.headers['content-type'] != 'text/html'

		# Parse response
		try: 
			res = self._parseJsonResponse(r.text, fix=fix, isJsonVars=jsonvar)
		except Exception as e:
			raise SpeedportHybridApi.ApiException('parsing json response failed', e)

		return res, r # res is dict of JsonVar, r is request object

	def _performRequest(self, method, uri, params, expectCode, noSession):
		"""Perform a http request with specified characteristics"""
		url = self._makeUrl(uri)

		# Check is session used and existing
		if not (noSession or self.hasSession()):
			raise SpeedportHybridApi.MissingSessionException()

		# Set cookie-header if needed
		if 'cookies' not in params and not noSession:
			params['cookies'] = {'challengev': self.Session.Challenge, 'derivedk': self.Session.DerivedKey, 'SessionID_R3': self.Session.ID}

		r = method(url, **params)

		# Assert http status code expectation (200 OK default)
		if expectCode is not None:
			assert r.status_code == expectCode # Expect proper status code in response

		return r

	def _getRequest(self, uri, expectCode=200, noSession=False, ownParams=None):
		"""Perform http-get request with specified characteristics"""
		params = ownParams or self.RequestParams.copy()

		return self._performRequest(requests.get, uri, params, expectCode, noSession)

	def _postRequest(self, uri, data, expectCode=200, noSession=False, ownParams=None):
		"""Perform http-post request with specified characteristics and data"""
		params = (ownParams or self.RequestParams).copy()

		params['data'] = data

		return self._performRequest(requests.post, uri, params, expectCode, noSession)

	def _parseJsonResponse(self, jsonString, isJsonVars=False, fix=True):
		"""Parse JSON-String. Invalid json is sanified per default. If requested the json is parsed as JsonVar-dict."""
		s = jsonString

		# Fix invalid json
		if fix:
			# Single Quotes to Double Quotes
			s = s.replace("'", '"')

			# Remove redundant trailing ',' 
			s = re.sub(r',(?=[\n\s]*[\]\}])|,(?=[\n\s]*$)', '', s)

			# Embed in an array
			if (re.match(r'^[\n\s]*\[', s) is None) and (re.match(r'\][\n\s]*$', s) is None):
				s = '[%s]' % s

		try:
			# Parse JSON String
			o = json.loads(s)

			# Fix response
			if fix:
				if isinstance(o, list) and len(o) == 1:
					o = o[0]

			# Parse vars if needed
			if isJsonVars:
				vx  = [SpeedportHybridApi.JsonVar(j) for j in o]
{'fraction_non_alphanumeric': 0.12217412217412217, 'fraction_numerical': 0.0032982890125747267, 'mean_word_length': 4.321389396709324, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2165450', 'n_tokens_mistral': 5075, 'n_tokens_neox': 4458, 'n_words': 1411}