def test_set_find(): hparams = HParams() hparams.set('expires', 3, 'Expires', '3') assert hparams.assemble() == 'Expires=3' hparams.set('q', QValue('0.1'), 'q', '0.1') assert hparams.assemble() == 'Expires=3;q=0.1' hparams.set_raw('MyCustomParam', None) assert hparams.assemble() == 'Expires=3;q=0.1;MyCustomParam' assert hparams.find('expires') == 3 assert hparams.find('Expires') == 3 assert hparams.find('q') == QValue('0.1') assert isinstance(hparams.find('q1'), HParamNotFound) assert hparams.find_raw('expires') == '3'
class ViaHeader(BaseSipHeader): """ Args: via (:obj: Header || str): VIA header to be parsed. Attributes: sent_protocol (:obj:SentProtocol): parsed sent protocol section of VIA header. _sent_by (:obj:SentBy): parsed sent by section of VIA header. hparams (:obj:HParams): parsed via parameters. """ SENT_BY_RX = re.compile(r'(.*?)(;.*)') IPV6_RX = re.compile(r'(\[.+?\])') def __init__(self, via=None): self.sent_protocol = dict() self._sent_by = dict() self.hparams = HParams() if via is not None: if isinstance(via, str): via_str = via else: raise ViaHeaderError( f'Cannot initialize ViaHeader: via should be type str, not {type(via)}' ) protocol_name, protocol_version, transport, sent_by_host, sent_by_port, via_params = \ self.parse_via(via_str) self.sent_protocol = SentProtocol(name=protocol_name, version=protocol_version, transport=transport) self._sent_by = SentBy(host=sent_by_host, port=sent_by_port) self.hparams = via_params @staticmethod def parse(via_hdr): return ViaHeader.topmost_via(via_hdr) def get_raw_param(self, param_name): """Get raw parameter value. Args: param_name (str): parameter name. Returns: :obj: raw parameter value. """ return self.hparams.find_raw(param_name) @property def ttl(self): """TTL value""" ttl = self.hparams.find(PARAM_TTL) if not isinstance(ttl, HParamNotFound): return ttl return None @ttl.setter def ttl(self, value): ttl, parsed_value = self.parse_known_param_fun(PARAM_TTL, value) self.hparams.set(PARAM_TTL, parsed_value, PARAM_TTL, value) @property def sent_by(self): """Sent by host and port values. Returns: :obj:SentBy: Parsed sent by section of VIA header. If port is not specified (is None), default port for transport is returned. """ ret_val = SentBy(host=self._sent_by.host, port=self._sent_by.port) if ret_val.port is None: ret_val.port = Transport.default_port(self.sent_protocol.transport) return ret_val @sent_by.setter def sent_by(self, value): if not isinstance(value, SentBy): raise NotImplemented self._sent_by = value @property def branch(self): """Branch value. Returns: :obj:Branch: Branch parameter. If no branch is specified, None is returned. """ branch = self.hparams.find(PARAM_BRANCH) if isinstance(branch, HParamNotFound): return None return branch @branch.setter def branch(self, value): self.hparams.set(PARAM_BRANCH, value, 'Branch', assemble_branch(branch=value)) @property def received(self): """Received value. Returns: :obj:ipaddress || str: Received parameter. If no received is specified, None is returned. """ received = self.hparams.find(PARAM_RECEIVED) print(f'received: ', received) if not isinstance(received, HParamNotFound): return received return None @property def maddr(self): """maddr value.""" maddr = self.hparams.find(PARAM_MADDR) if not isinstance(maddr, HParamNotFound): return maddr return None @staticmethod def assemble_param_value(name, value): """Get parameter's value string representation. Args: name (str): parameter name. value (:obj:): parameter value. Returns: str: Value of specified parameter. """ if name == PARAM_RPORT and value is True: value = None elif name == PARAM_BRANCH: value = assemble_branch(value) return value def set_param(self, name, value): """Sets RECEIVED or RPORT parameter. Args: name (str): parameter name value (str || int): valid host str or rport int in range 1..65535 Raises: ViaHeaderError if RECEIVED value is not a valid IPv4 or IPv6 host. ViaHeaderError if RPORT is not a integer in range 1..65535. """ if name == PARAM_RECEIVED: host = value if isinstance(value, str): try: host = PARSER.parse_host(value) except Exception as e: raise ViaHeaderError( f'Cannot set Via param {name}={value}: invalid host {e}' ) if isinstance(value, IPv6Address): value = f'[{value}]' if isinstance(host, IPv4Address) or isinstance(host, IPv6Address): self.hparams.set( PARAM_RECEIVED, host, PARAM_RECEIVED, ViaHeader.assemble_param_value(PARAM_RECEIVED, value)) else: raise ViaHeaderError( f'Cannot set Via RECEIVED param {name}={value}: invalid IPv4 or IPv6 host' ) elif name == PARAM_RPORT: if (isinstance(value, bool) and value is False) or \ (not isinstance(value, int) or value < 1 or value > 65535): raise ViaHeaderError( f'Cannot set Via RPORT param {name}={value}: invalid rport' ) self.hparams.set( PARAM_RPORT, value, PARAM_RPORT, ViaHeader.assemble_param_value(PARAM_RPORT, value)) @staticmethod def parse_transport(transport_str): """Parse transport from string that starts with transport description. Args: transport_str (str): tail of Via header value that contains transport information. Returns: :obj:tuple of :obj:Transport, :obj:str """ transport, rest = parse_token(transport_str) try: transport = Transport(transport) return transport, rest except Exception as e: raise e @staticmethod def parse_sent_protocol(via_string): """Parse sent protocol from Via header string. Args: via_string (str): Via header value. Returns: :obj:tuple of str:protocol_name, str:protocol_version, :obj:Transport, str:rest: parsed protocol name, protocol version, protocol transport and unparsed rest. """ try: protocol_name, rest = parse_token(via_string) except Exception as e: raise ViaHeaderError( f'Cannot parse protocol name from Via header "{via_string}": {e}' ) try: slash, rest = parse_slash(rest) protocol_version, rest = parse_token(rest) except Exception as e: raise ViaHeaderError( f'Cannot parse protocol version from Via header "{via_string}": {e}' ) try: slash, rest = parse_slash(rest) transport, rest = ViaHeader.parse_transport(rest) except Exception as e: raise ViaHeaderError( f'Cannot parse transport from Via header "{via_string}": {e}') return protocol_name, protocol_version, transport, rest @staticmethod def parse_via(via_str): """Parse Via header. Args: via_str (str): Via header value. Returns: str:protocol_name, str:protocol_version, Transport:transport, str:sent_by_host, str:sent_by_port, HParams:via_params """ try: protocol_name, protocol_version, transport, rest = ViaHeader.parse_sent_protocol( via_str) sent_by_host, sent_by_port, rest = ViaHeader.parse_sent_by( rest.strip()) via_params, rest = ViaHeader.parse_via_params(rest.strip()) return protocol_name, protocol_version, transport, sent_by_host, sent_by_port, via_params except Exception as e: raise e @staticmethod def parse_sent_by(sentby_str): """Parses Via header sent by value out of string. Args: sentby_str (str): string that starts with sent_by host and port Returns: host, int:port and unparsed str:rest Raises: ViaHeaderError if sentby_str host and port are not rfc compliant. """ try: rx_match = ViaHeader.SENT_BY_RX.match(sentby_str) if rx_match: host_port, rest = rx_match.group(1).strip(), rx_match.group(2) else: host_port, rest = sentby_str.strip(), '' print( f'ViaHeader.parse_sent_by({sentby_str}): hostport {host_port}') h, p = ViaHeader.parse_sent_by_host_port(host_port) return h, p, rest except Exception as e: raise ViaHeaderError( f'Cannot parse Via sent by parameter from {sentby_str}: {e}') @staticmethod def parse_sent_by_host_port(host_port): """Parses Via header sent by host and port values out of string. Args: host_port (str) Returns: :obj:str||ipaddress:host, int:port Raises: ViaHeaderError if sentby_str host and port are not rfc compliant. """ print(f'ViaHeader.parse_sent_by_host_port({host_port})') try: return PARSER.parse_host(host_port), PARSER.parse_port(host_port) except Exception as e: raise ViaHeaderError( f'Cannot parse via header host and port {host_port}: {e}') @staticmethod def parse_via_params(via_params_str): """Parses Via header parameters out of string. Args: via_params_str (str) Returns: :obj:HParams: header parameters container with parsed parameters. Raises: ViaHeaderError if parameters cannot be parsed. """ if not via_params_str or not via_params_str.startswith(';'): return HParams(), via_params_str else: hparams = HParams() try: hparams.parse_raw(via_params_str[1:]) except Exception as e: raise ViaHeaderError( f'Cannot parse Via params from {via_params_str}: {e}') hparams.parse_known(known_function=ViaHeader.parse_known_param_fun) rest = via_params_str.lstrip(hparams.assemble()) return hparams, rest @staticmethod def parse_known_param_fun(param, value): """Filtering function that is passed as argument to HParams.parse_known method. Args: param (str): parameter name. value (str): parameter value. Returns: str:parsed_name, :obj:parsed_value None, None: if lowercase parameter name is not in (PARAM_TTL, PARAM_RECEIVED, PARAM_MADDR, PARAM_BRANCH, PARAM_RPORT) """ if param == PARAM_TTL: ttl, rest = parse_non_negative_integer(value.strip()) if rest or ttl < 0 or ttl > 255: raise ViaHeaderError( f'Cannot parse via header TTL {param}={value}: value should be 0..255 integer' ) return PARAM_TTL, ttl elif param == PARAM_RECEIVED: try: host = PARSER.parse_host(value) except Exception as e: raise ViaHeaderError( f'Cannot parse Via RECEIVED {param}={value}: invalid host {e}' ) if isinstance(host, IPv4Address) or isinstance(host, IPv6Address): return PARAM_RECEIVED, host else: raise ViaHeaderError( f'Cannot set Via RECEIVED {param}={value}: invalid IPv4 or IPv6 host' ) elif param == PARAM_MADDR: try: host = PARSER.parse_host(value) except Exception as e: raise ViaHeaderError( f'Cannot parse Via MADDR {param}={value}: invalid host {e}' ) return PARAM_MADDR, host elif param == PARAM_BRANCH: try: branch, rest = parse_token(value) if rest: raise ViaHeaderError( f'Cannot parse Via BRANCH {param}={value}: value should be token' ) return PARAM_BRANCH, Branch(branch) except Exception as e: raise ViaHeaderError( f'Cannot parse Via BRANCH {param}={value}: {e}') elif param == PARAM_RPORT: if value is None: port, rest = True, '' else: port, rest = parse_non_negative_integer(value) if rest or (port is not None and (port <= 0 or port > 65535)): raise ViaHeaderError( f'Cannot parse via header RPORT {param}={value}: value should be 1..65535 integer' ) return PARAM_RPORT, port else: return None, None @staticmethod def topmost_via(via_hdr): """Makes a ViaHeader instance from Header object's topmost value. Args: via_hdr (obj:Header): header to be parsed. Returns: obj:ViaHeader: Via header parsed from first element of via_hdr object. Raises: ViaHeaderError if via_hdr parameter is not a Header object or if via_hdr object doesn't contain any values. """ if not isinstance(via_hdr, Header): raise ViaHeaderError( f'Cannot parse topmost via: via_header should be of type Header not {type(via_hdr)}' ) if not via_hdr.values: raise ViaHeaderError(f'Cannot parse topmost via: no via') return ViaHeader(via_hdr.values[0]) @staticmethod def make_param_key(hparams): """Helper function that returns comparable object of reduced HParams. Args: hparams (obj:HParams): header parameters container Returns: :obj:list: list of param_name, param_value pairs that can be compared to similar list. """ params_key = [] for param_name, value in hparams.to_list(): if param_name == PARAM_TTL: params_key.append((PARAM_TTL, value)) elif param_name == PARAM_BRANCH: params_key.append((PARAM_BRANCH, value.make_key())) elif param_name == PARAM_MADDR: if isinstance(value, (ipaddress.IPv4Address, ipaddress.IPv6Address)): app_val = value else: app_val = value.lower() params_key.append(app_val) elif param_name == PARAM_RPORT: if isinstance(value, bool): params_key.append(f'{value}') else: params_key.append(value) else: params_key.append((param_name.lower(), value)) return params_key def __eq__(self, other): if isinstance(other, ViaHeader): return self.sent_protocol == other.sent_protocol and self.sent_by == other.sent_by and \ self.make_param_key(self.hparams) == self.make_param_key(other.hparams) return NotImplemented def assemble(self): """Makes a string that represents Via header. Returns: str: Via header string. """ hparams_str = self.hparams.assemble() if hparams_str: hparams_str = f';{hparams_str}' ret_val = f'{self.sent_protocol} {self.sent_by}{hparams_str}' return ret_val def __repr__(self): return self.assemble() def has_rport(self): """Is RPORT defined in Via header. Returns: bool: True if rport defined in Via parameters, False otherwise. """ rport = self.hparams.find(PARAM_RPORT) if not isinstance(rport, HParamNotFound): return True return False @property def rport(self): """RPORT value. Returns: :obj:ipaddress || str: Received parameter. If no received is specified, None is returned. """ rport = self.hparams.find(PARAM_RPORT) if not isinstance(rport, HParamNotFound): return rport return None def build(self, header_name): raise NotImplementedError
class ContactHeader(object): def __init__(self, contact_string=None): self.display_name = None self.uri = Uri() self.uri.parser_impl = SIPUriParserUnicode() self.params = HParams() self.rest = None if contact_string is not None: self.display_name, self.uri, self.params, self.rest = self.parse_hdr( contact_string) def parse_contact_params(self, contact_params): if contact_params.startswith(';'): return self.do_parse_contact_params(contact_params[1:]) return HParams(), '' @staticmethod def do_parse_contact_params(string): try: hparams = HParams() params_list = parse_params(string, ';') for k, v in params_list: if k.lower() in (EXPIRES, Q): hparams.set(k.lower(), ContactHeader.parse_param(k.lower(), v), k, v) elif check_token(k.lower()): hparams.set_raw(k, v) else: raise ContactHeaderError(f'Key {k}={v} is not a token.') return hparams, '' except (ParserAUXError, HParamsError, ContactHeaderError) as e: raise ContactHeaderError( f'Cannot parse contact header {string}: {e}') def parse_hdr(self, string): try: nameaddr = NameAddress(string) display_name = nameaddr.display_name uri = nameaddr.uri except NameAddressError as e: raise ContactHeaderError( f'Cannot parse contact header {string} nameaddress part: {e}') contact_params, rest = self.parse_contact_params(nameaddr.rest) return display_name, uri, contact_params, rest def get_expires(self, default=None): expires = self.params.find(EXPIRES) if isinstance(expires, HParamNotFound): return default return expires def set_expires(self, expires_val): if is_integer(expires_val): self.params.set(EXPIRES, expires_val, EXPIRES, expires_val) else: raise ContactHeaderError( f'Cannot set expires parameter {expires_val}: not an integer') def get_qvalue(self, default=None): qval = self.params.find('q') if isinstance(qval, HParamNotFound): return default return qval def set_qvalue(self, qvalue): self.params.set(Q, qvalue, Q, qvalue) @staticmethod def parse_param(param_name, param_value): if param_name == EXPIRES: try: return int(param_value) except ValueError: raise ContactHeaderError( f'Cannot parse expires param: value {param_value} is not int.' ) if param_name == Q: try: return QValue(param_value) except QValueError as e: raise ContactHeaderError(f'Cannot parse q param: {e}') def __eq__(self, other): if isinstance(other, ContactHeader): return self.display_name == other.display_name and self.uri == other.uri and self.params == other.params return NotImplemented def assemble(self): assembled_hparams = self.params.assemble() if assembled_hparams: assembled_hparams = f';{assembled_hparams}' return f'{NameAddress.assemble(self.display_name, self.uri)}{assembled_hparams}' def set_param(self, name, value): if name.lower() in (EXPIRES, Q): try: parsed_value = self.parse_param(name, value) self.params.set(name.lower(), parsed_value, name, value) except ContactHeaderError as e: raise ContactHeaderError( f'Cannot set param {name}={value}: {e}') elif check_token(name.lower()): self.params.set_raw(name, value) else: raise ContactHeaderError( f'Cannot set param {name}={value}: invalid param.') def get_param(self, name): return self.params.find_raw(name) def __repr__(self): return self.assemble()