def _parse_file(descriptor_file, validate=False, **kwargs): """ Iterates over the hidden service descriptors in a file. :param file descriptor_file: file with descriptor content :param bool validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param dict kwargs: additional arguments for the descriptor constructor :returns: iterator for :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor` instances in the file :raises: * **ValueError** if the contents is malformed and validate is **True** * **IOError** if the file can't be read """ while True: descriptor_content = _read_until_keywords('signature', descriptor_file) # we've reached the 'signature', now include the pgp style block block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True) if descriptor_content: if descriptor_content[0].startswith(b'@type'): descriptor_content = descriptor_content[1:] yield HiddenServiceDescriptor(bytes.join(b'', descriptor_content), validate, **kwargs) else: break # done parsing file
def _parse_file(descriptor_file, validate = False, **kwargs): """ Iterates over the hidden service descriptors in a file. :param file descriptor_file: file with descriptor content :param bool validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param dict kwargs: additional arguments for the descriptor constructor :returns: iterator for :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor` instances in the file :raises: * **ValueError** if the contents is malformed and validate is **True** * **IOError** if the file can't be read """ while True: descriptor_content = _read_until_keywords('signature', descriptor_file) # we've reached the 'signature', now include the pgp style block block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True) if descriptor_content: if descriptor_content[0].startswith(b'@type'): descriptor_content = descriptor_content[1:] yield HiddenServiceDescriptor(bytes.join(b'', descriptor_content), validate, **kwargs) else: break # done parsing file
def _parse_file( tordnsel_file: BinaryIO, validate: bool = False, **kwargs: Any) -> Iterator['stem.descriptor.tordnsel.TorDNSEL']: """ Iterates over a tordnsel file. :returns: iterator for :class:`~stem.descriptor.tordnsel.TorDNSEL` instances in the file :raises: * **ValueError** if the contents is malformed and validate is **True** * **OSError** if the file can't be read """ if kwargs: raise ValueError("TorDNSEL doesn't support additional arguments: %s" % kwargs) # skip content prior to the first ExitNode _read_until_keywords('ExitNode', tordnsel_file, skip=True) while True: contents = _read_until_keywords('ExitAddress', tordnsel_file) contents += _read_until_keywords('ExitNode', tordnsel_file) if contents: yield TorDNSEL(bytes.join(b'', contents), validate) else: break # done parsing file
def _parse_file(descriptor_file, is_bridge = False, validate = True, **kwargs): """ Iterates over the extra-info descriptors in a file. :param file descriptor_file: file with descriptor content :param bool is_bridge: parses the file as being a bridge descriptor :param bool validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param dict kwargs: additional arguments for the descriptor constructor :returns: iterator for :class:`~stem.descriptor.extrainfo_descriptor.ExtraInfoDescriptor` instances in the file :raises: * **ValueError** if the contents is malformed and validate is **True** * **IOError** if the file can't be read """ while True: extrainfo_content = _read_until_keywords("router-signature", descriptor_file) # we've reached the 'router-signature', now include the pgp style block block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] extrainfo_content += _read_until_keywords(block_end_prefix, descriptor_file, True) if extrainfo_content: if is_bridge: yield BridgeExtraInfoDescriptor(bytes.join(b"", extrainfo_content), validate, **kwargs) else: yield RelayExtraInfoDescriptor(bytes.join(b"", extrainfo_content), validate, **kwargs) else: break # done parsing file
def _parse_file(descriptor_file, is_bridge = False, validate = True, **kwargs): """ Iterates over the extra-info descriptors in a file. :param file descriptor_file: file with descriptor content :param bool is_bridge: parses the file as being a bridge descriptor :param bool validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param dict kwargs: additional arguments for the descriptor constructor :returns: iterator for :class:`~stem.descriptor.extrainfo_descriptor.ExtraInfoDescriptor` instances in the file :raises: * **ValueError** if the contents is malformed and validate is **True** * **IOError** if the file can't be read """ while True: extrainfo_content = _read_until_keywords('router-signature', descriptor_file) # we've reached the 'router-signature', now include the pgp style block block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] extrainfo_content += _read_until_keywords(block_end_prefix, descriptor_file, True) if extrainfo_content: if extrainfo_content[0].startswith(b'@type'): extrainfo_content = extrainfo_content[1:] if is_bridge: yield BridgeExtraInfoDescriptor(bytes.join(b'', extrainfo_content), validate, **kwargs) else: yield RelayExtraInfoDescriptor(bytes.join(b'', extrainfo_content), validate, **kwargs) else: break # done parsing file
def _parse_file(descriptor_file: BinaryIO, validate: bool = False, **kwargs: Any) -> Iterator['stem.descriptor.microdescriptor.Microdescriptor']: """ Iterates over the microdescriptors in a file. :param descriptor_file: file with descriptor content :param validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param kwargs: additional arguments for the descriptor constructor :returns: iterator for Microdescriptor instances in the file :raises: * **ValueError** if the contents is malformed and validate is True * **IOError** if the file can't be read """ if kwargs: raise ValueError('BUG: keyword arguments unused by microdescriptors') while True: annotations = _read_until_keywords('onion-key', descriptor_file) # read until we reach an annotation or onion-key line descriptor_lines = [] # read the onion-key line, done if we're at the end of the document onion_key_line = descriptor_file.readline() if onion_key_line: descriptor_lines.append(onion_key_line) else: break while True: last_position = descriptor_file.tell() line = descriptor_file.readline() if not line: break # EOF elif line.startswith(b'@') or line.startswith(b'onion-key'): descriptor_file.seek(last_position) break else: descriptor_lines.append(line) if descriptor_lines: if descriptor_lines[0].startswith(b'@type'): descriptor_lines = descriptor_lines[1:] # strip newlines from annotations annotations = list(map(bytes.strip, annotations)) descriptor_text = bytes.join(b'', descriptor_lines) yield Microdescriptor(descriptor_text, validate, annotations) else: break # done parsing descriptors
def _parse_file(descriptor_file, validate=False, onion_address=None, **kwargs): while True: descriptor_content = _read_until_keywords('signature', descriptor_file) # we've reached the 'signature', now include the pgp style block block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True) if descriptor_content: if descriptor_content[0].startswith(b'@type'): descriptor_content = descriptor_content[1:] yield Hsv3Descriptor(bytes.join(b'', descriptor_content), validate, onion_address=onion_address, **kwargs) else: break # done parsing file
def _parse_file(descriptor_file, validate=True, **kwargs): """ Iterates over the microdescriptors in a file. :param file descriptor_file: file with descriptor content :param bool validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param dict kwargs: additional arguments for the descriptor constructor :returns: iterator for Microdescriptor instances in the file :raises: * **ValueError** if the contents is malformed and validate is True * **IOError** if the file can't be read """ while True: annotations = _read_until_keywords("onion-key", descriptor_file) # read until we reach an annotation or onion-key line descriptor_lines = [] # read the onion-key line, done if we're at the end of the document onion_key_line = descriptor_file.readline() if onion_key_line: descriptor_lines.append(onion_key_line) else: break while True: last_position = descriptor_file.tell() line = descriptor_file.readline() if not line: break # EOF elif line.startswith(b"@") or line.startswith(b"onion-key"): descriptor_file.seek(last_position) break else: descriptor_lines.append(line) if descriptor_lines: if descriptor_lines[0].startswith(b"@type"): descriptor_lines = descriptor_lines[1:] # strip newlines from annotations annotations = map(bytes.strip, annotations) descriptor_text = bytes.join(b"", descriptor_lines) yield Microdescriptor(descriptor_text, validate, annotations, **kwargs) else: break # done parsing descriptors
def _parse_file(descriptor_file: BinaryIO, is_bridge = False, validate = False, **kwargs: Any) -> Iterator['stem.descriptor.extrainfo_descriptor.ExtraInfoDescriptor']: """ Iterates over the extra-info descriptors in a file. :param descriptor_file: file with descriptor content :param is_bridge: parses the file as being a bridge descriptor :param validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param kwargs: additional arguments for the descriptor constructor :returns: iterator for :class:`~stem.descriptor.extrainfo_descriptor.ExtraInfoDescriptor` instances in the file :raises: * **ValueError** if the contents is malformed and validate is **True** * **IOError** if the file can't be read """ if kwargs: raise ValueError('BUG: keyword arguments unused by extrainfo descriptors') while True: if not is_bridge: extrainfo_content = _read_until_keywords('router-signature', descriptor_file) # we've reached the 'router-signature', now include the pgp style block block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] extrainfo_content += _read_until_keywords(block_end_prefix, descriptor_file, True) else: extrainfo_content = _read_until_keywords('router-digest', descriptor_file, True) if extrainfo_content: if extrainfo_content[0].startswith(b'@type'): extrainfo_content = extrainfo_content[1:] if is_bridge: yield BridgeExtraInfoDescriptor(bytes.join(b'', extrainfo_content), validate) else: yield RelayExtraInfoDescriptor(bytes.join(b'', extrainfo_content), validate) else: break # done parsing file
def _parse_file(descriptor_file, desc_type=None, validate=False, **kwargs): """ Iterates over the hidden service descriptors in a file. :param file descriptor_file: file with descriptor content :param class desc_type: BaseHiddenServiceDescriptor subclass :param bool validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param dict kwargs: additional arguments for the descriptor constructor :returns: iterator for :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptorV2` instances in the file :raises: * **ValueError** if the contents is malformed and validate is **True** * **IOError** if the file can't be read """ if desc_type is None: desc_type = HiddenServiceDescriptorV2 # Hidden service v3 ends with a signature line, whereas v2 has a pgp style # block following it. while True: descriptor_content = _read_until_keywords('signature', descriptor_file, True) if desc_type == HiddenServiceDescriptorV2: block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True) if descriptor_content: if descriptor_content[0].startswith(b'@type'): descriptor_content = descriptor_content[1:] yield desc_type(bytes.join(b'', descriptor_content), validate, **kwargs) else: break # done parsing file
def _parse_file(descriptor_file, validate=True, **kwargs): """ Iterates over the microdescriptors in a file. :param file descriptor_file: file with descriptor content :param bool validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param dict kwargs: additional arguments for the descriptor constructor :returns: iterator for Microdescriptor instances in the file :raises: * **ValueError** if the contents is malformed and validate is True * **IOError** if the file can't be read """ while True: annotations = _read_until_keywords("onion-key", descriptor_file) # read until we reach an annotation or onion-key line descriptor_lines = [] # read the onion-key line, done if we're at the end of the document onion_key_line = descriptor_file.readline() if onion_key_line: descriptor_lines.append(onion_key_line) else: break while True: last_position = descriptor_file.tell() line = descriptor_file.readline() if not line: break # EOF elif line.startswith(b"@") or line.startswith(b"onion-key"): descriptor_file.seek(last_position) break else: descriptor_lines.append(line) if descriptor_lines: # strip newlines from annotations annotations = map(bytes.strip, annotations) descriptor_text = bytes.join(b"", descriptor_lines) yield Microdescriptor(descriptor_text, validate, annotations, **kwargs) else: break # done parsing descriptors
def _parse_introduction_points(content): """ Provides the parsed list of IntroductionPoints for the unencrypted content. """ introduction_points = [] content_io = io.BytesIO(content) while True: content = b''.join(_read_until_keywords('introduction-point', content_io, ignore_first = True)) if not content: break # reached the end attr = dict(INTRODUCTION_POINTS_ATTR) entries = _get_descriptor_components(content, False) for keyword, values in list(entries.items()): value, block_type, block_contents = values[0] if keyword in SINGLE_INTRODUCTION_POINT_FIELDS and len(values) > 1: raise ValueError("'%s' can only appear once in an introduction-point block, but appeared %i times" % (keyword, len(values))) if keyword == 'introduction-point': attr['identifier'] = value elif keyword == 'ip-address': if not stem.util.connection.is_valid_ipv4_address(value): raise ValueError("'%s' is an invalid IPv4 address" % value) attr['address'] = value elif keyword == 'onion-port': if not stem.util.connection.is_valid_port(value): raise ValueError("'%s' is an invalid port" % value) attr['port'] = int(value) elif keyword == 'onion-key': attr['onion_key'] = block_contents elif keyword == 'service-key': attr['service_key'] = block_contents elif keyword == 'intro-authentication': auth_entries = [] for auth_value, _, _ in values: if ' ' not in auth_value: raise ValueError("We expected 'intro-authentication [auth_type] [auth_data]', but had '%s'" % auth_value) auth_type, auth_data = auth_value.split(' ')[:2] auth_entries.append((auth_type, auth_data)) introduction_points.append(IntroductionPoints(**attr)) return introduction_points
def _parse_introduction_points(content): """ Provides the parsed list of IntroductionPoints for the unencrypted content. """ introduction_points = [] content_io = io.BytesIO(content) while True: content = b''.join(_read_until_keywords('introduction-point', content_io, ignore_first = True)) if not content: break # reached the end attr = dict(INTRODUCTION_POINTS_ATTR) entries = _descriptor_components(content, False) for keyword, values in list(entries.items()): value, block_type, block_contents = values[0] if keyword in SINGLE_INTRODUCTION_POINT_FIELDS and len(values) > 1: raise ValueError("'%s' can only appear once in an introduction-point block, but appeared %i times" % (keyword, len(values))) if keyword == 'introduction-point': attr['identifier'] = value elif keyword == 'ip-address': if not stem.util.connection.is_valid_ipv4_address(value): raise ValueError("'%s' is an invalid IPv4 address" % value) attr['address'] = value elif keyword == 'onion-port': if not stem.util.connection.is_valid_port(value): raise ValueError("'%s' is an invalid port" % value) attr['port'] = int(value) elif keyword == 'onion-key': attr['onion_key'] = block_contents elif keyword == 'service-key': attr['service_key'] = block_contents elif keyword == 'intro-authentication': auth_entries = [] for auth_value, _, _ in values: if ' ' not in auth_value: raise ValueError("We expected 'intro-authentication [auth_type] [auth_data]', but had '%s'" % auth_value) auth_type, auth_data = auth_value.split(' ')[:2] auth_entries.append((auth_type, auth_data)) introduction_points.append(IntroductionPoints(**attr)) return introduction_points
def _parse_file(tordnsel_file, validate = True, **kwargs): """ Iterates over a tordnsel file. :returns: iterator for :class:`~stem.descriptor.tordnsel.TorDNSEL` instances in the file :raises: * **ValueError** if the contents is malformed and validate is **True** * **IOError** if the file can't be read """ # skip content prior to the first ExitNode _read_until_keywords('ExitNode', tordnsel_file, skip = True) while True: contents = _read_until_keywords('ExitAddress', tordnsel_file) contents += _read_until_keywords('ExitNode', tordnsel_file) if contents: yield TorDNSEL(bytes.join(b'', contents), validate, **kwargs) else: break # done parsing file
def _parse_file(tordnsel_file, validate=True, **kwargs): """ Iterates over a tordnsel file. :returns: iterator for :class:`~stem.descriptor.tordnsel.TorDNSEL` instances in the file :raises: * **ValueError** if the contents is malformed and validate is **True** * **IOError** if the file can't be read """ # skip content prior to the first ExitNode _read_until_keywords('ExitNode', tordnsel_file, skip=True) while True: contents = _read_until_keywords('ExitAddress', tordnsel_file) contents += _read_until_keywords('ExitNode', tordnsel_file) if contents: yield TorDNSEL(bytes.join(b'', contents), validate, **kwargs) else: break # done parsing file
def _parse_introduction_points(content): """ Provides the parsed list of IntroductionPoints for the unencrypted content. """ introduction_points = [] content_io = io.BytesIO(content) while True: content = b''.join( _read_until_keywords('introduction-point', content_io, ignore_first=True)) if not content: break # reached the end attr = dict(INTRODUCTION_POINTS_ATTR) entries = _descriptor_components(content, False) for keyword, values in list(entries.items()): value, block_type, block_contents = values[0] if keyword in SINGLE_INTRODUCTION_POINT_FIELDS and len( values) > 1: raise ValueError( "'%s' can only appear once in an introduction-point block, but appeared %i times" % (keyword, len(values))) elif keyword == 'introduction-point': attr['link_specifier'] = value elif keyword == 'onion-key': attr['onion_key'] = value elif keyword == 'auth-key': attr[ 'auth_key'] = stem.descriptor.certificate.Ed25519Certificate.parse( ''.join(block_contents.splitlines()[1:-1])) elif keyword == 'enc-key': attr['enc_key'] = value elif keyword == 'enc-key-cert': attr[ 'enc_key_cert'] = stem.descriptor.certificate.Ed25519Certificate.parse( ''.join(block_contents.splitlines()[1:-1])) elif keyword == 'legacy-key': attr['legacy_key'] = block_contents elif keyword == 'legacy-key-cert': attr['legacy_key_cert'] = block_contents introduction_points.append(IntroductionPoints(**attr)) return introduction_points
def _parse_file(descriptor_file, is_bridge=False, validate=True, **kwargs): """ Iterates over the server descriptors in a file. :param file descriptor_file: file with descriptor content :param bool is_bridge: parses the file as being a bridge descriptor :param bool validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param dict kwargs: additional arguments for the descriptor constructor :returns: iterator for ServerDescriptor instances in the file :raises: * **ValueError** if the contents is malformed and validate is True * **IOError** if the file can't be read """ # Handler for relay descriptors # # Cached descriptors consist of annotations followed by the descriptor # itself. For instance... # # @downloaded-at 2012-03-14 16:31:05 # @source "145.53.65.130" # router caerSidi 71.35.143.157 9001 0 0 # platform Tor 0.2.1.30 on Linux x86_64 # <rest of the descriptor content> # router-signature # -----BEGIN SIGNATURE----- # <signature for the above descriptor> # -----END SIGNATURE----- # # Metrics descriptor files are the same, but lack any annotations. The # following simply does the following... # # - parse as annotations until we get to "router" # - parse as descriptor content until we get to "router-signature" followed # by the end of the signature block # - construct a descriptor and provide it back to the caller # # Any annotations after the last server descriptor is ignored (never provided # to the caller). while True: annotations = _read_until_keywords("router", descriptor_file) descriptor_content = _read_until_keywords("router-signature", descriptor_file) # we've reached the 'router-signature', now include the pgp style block block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True) if descriptor_content: # strip newlines from annotations annotations = map(bytes.strip, annotations) descriptor_text = bytes.join(b"", descriptor_content) if is_bridge: yield BridgeDescriptor(descriptor_text, validate, annotations, **kwargs) else: yield RelayDescriptor(descriptor_text, validate, annotations, **kwargs) else: if validate and annotations: orphaned_annotations = stem.util.str_tools._to_unicode( b'\n'.join(annotations)) raise ValueError( 'Content conform to being a server descriptor:\n%s' % orphaned_annotations) break # done parsing descriptors
def _parse_file(document_file, validate, entry_class, entry_keyword='r', start_position=None, end_position=None, section_end_keywords=(), extra_args=()): """ Reads a range of the document_file containing some number of entry_class instances. We deliminate the entry_class entries by the keyword on their first line (entry_keyword). When finished the document is left at the end_position. Either an end_position or section_end_keywords must be provided. :param file document_file: file with network status document content :param bool validate: checks the validity of the document's contents if **True**, skips these checks otherwise :param class entry_class: class to construct instance for :param str entry_keyword: first keyword for the entry instances :param int start_position: start of the section, default is the current position :param int end_position: end of the section :param tuple section_end_keywords: keyword(s) that deliminate the end of the section if no end_position was provided :param tuple extra_args: extra arguments for the entry_class (after the content and validate flag) :returns: iterator over entry_class instances :raises: * **ValueError** if the contents is malformed and validate is **True** * **IOError** if the file can't be read """ if start_position: document_file.seek(start_position) else: start_position = document_file.tell() # check if we're starting at the end of the section (ie, there's no entries to read) if section_end_keywords: first_keyword = None line_match = KEYWORD_LINE.match( stem.util.str_tools._to_unicode(document_file.readline())) if line_match: first_keyword = line_match.groups()[0] document_file.seek(start_position) if first_keyword in section_end_keywords: return while end_position is None or document_file.tell() < end_position: desc_lines, ending_keyword = _read_until_keywords( (entry_keyword, ) + section_end_keywords, document_file, ignore_first=True, end_position=end_position, include_ending_keyword=True) desc_content = bytes.join(b'', desc_lines) if desc_content: yield entry_class(desc_content, validate, *extra_args) # check if we stopped at the end of the section if ending_keyword in section_end_keywords: break else: break
def _parse_file(document_file, validate, entry_class, entry_keyword = "r", start_position = None, end_position = None, section_end_keywords = (), extra_args = ()): """ Reads a range of the document_file containing some number of entry_class instances. We deliminate the entry_class entries by the keyword on their first line (entry_keyword). When finished the document is left at the end_position. Either an end_position or section_end_keywords must be provided. :param file document_file: file with network status document content :param bool validate: checks the validity of the document's contents if **True**, skips these checks otherwise :param class entry_class: class to construct instance for :param str entry_keyword: first keyword for the entry instances :param int start_position: start of the section, default is the current position :param int end_position: end of the section :param tuple section_end_keywords: keyword(s) that deliminate the end of the section if no end_position was provided :param tuple extra_args: extra arguments for the entry_class (after the content and validate flag) :returns: iterator over entry_class instances :raises: * **ValueError** if the contents is malformed and validate is **True** * **IOError** if the file can't be read """ if start_position: document_file.seek(start_position) else: start_position = document_file.tell() # check if we're starting at the end of the section (ie, there's no entries to read) if section_end_keywords: first_keyword = None line_match = KEYWORD_LINE.match(stem.util.str_tools._to_unicode(document_file.readline())) if line_match: first_keyword = line_match.groups()[0] document_file.seek(start_position) if first_keyword in section_end_keywords: return while end_position is None or document_file.tell() < end_position: desc_lines, ending_keyword = _read_until_keywords( (entry_keyword,) + section_end_keywords, document_file, ignore_first = True, end_position = end_position, include_ending_keyword = True ) desc_content = bytes.join(b"", desc_lines) if desc_content: yield entry_class(desc_content, validate, *extra_args) # check if we stopped at the end of the section if ending_keyword in section_end_keywords: break else: break
def _parse_file(descriptor_file, is_bridge = False, validate = True, **kwargs): """ Iterates over the server descriptors in a file. :param file descriptor_file: file with descriptor content :param bool is_bridge: parses the file as being a bridge descriptor :param bool validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param dict kwargs: additional arguments for the descriptor constructor :returns: iterator for ServerDescriptor instances in the file :raises: * **ValueError** if the contents is malformed and validate is True * **IOError** if the file can't be read """ # Handler for relay descriptors # # Cached descriptors consist of annotations followed by the descriptor # itself. For instance... # # @downloaded-at 2012-03-14 16:31:05 # @source "145.53.65.130" # router caerSidi 71.35.143.157 9001 0 0 # platform Tor 0.2.1.30 on Linux x86_64 # <rest of the descriptor content> # router-signature # -----BEGIN SIGNATURE----- # <signature for the above descriptor> # -----END SIGNATURE----- # # Metrics descriptor files are the same, but lack any annotations. The # following simply does the following... # # - parse as annotations until we get to 'router' # - parse as descriptor content until we get to 'router-signature' followed # by the end of the signature block # - construct a descriptor and provide it back to the caller # # Any annotations after the last server descriptor is ignored (never provided # to the caller). while True: annotations = _read_until_keywords('router', descriptor_file) descriptor_content = _read_until_keywords('router-signature', descriptor_file) # we've reached the 'router-signature', now include the pgp style block block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True) if descriptor_content: if descriptor_content[0].startswith(b'@type'): descriptor_content = descriptor_content[1:] # strip newlines from annotations annotations = map(bytes.strip, annotations) descriptor_text = bytes.join(b'', descriptor_content) if is_bridge: yield BridgeDescriptor(descriptor_text, validate, annotations, **kwargs) else: yield RelayDescriptor(descriptor_text, validate, annotations, **kwargs) else: if validate and annotations: orphaned_annotations = stem.util.str_tools._to_unicode(b'\n'.join(annotations)) raise ValueError('Content conform to being a server descriptor:\n%s' % orphaned_annotations) break # done parsing descriptors
def _parse_file( descriptor_file: BinaryIO, is_bridge: bool = False, validate: bool = False, **kwargs: Any ) -> Iterator['stem.descriptor.server_descriptor.ServerDescriptor']: """ Iterates over the server descriptors in a file. :param descriptor_file: file with descriptor content :param is_bridge: parses the file as being a bridge descriptor :param validate: checks the validity of the descriptor's content if **True**, skips these checks otherwise :param kwargs: additional arguments for the descriptor constructor :returns: iterator for ServerDescriptor instances in the file :raises: * **ValueError** if the contents is malformed and validate is True * **IOError** if the file can't be read """ # Handler for relay descriptors # # Cached descriptors consist of annotations followed by the descriptor # itself. For instance... # # @downloaded-at 2012-03-14 16:31:05 # @source "145.53.65.130" # router caerSidi 71.35.143.157 9001 0 0 # platform Tor 0.2.1.30 on Linux x86_64 # <rest of the descriptor content> # router-signature # -----BEGIN SIGNATURE----- # <signature for the above descriptor> # -----END SIGNATURE----- # # Metrics descriptor files are the same, but lack any annotations. The # following simply does the following... # # - parse as annotations until we get to 'router' # - parse as descriptor content until we get to 'router-signature' followed # by the end of the signature block # - construct a descriptor and provide it back to the caller # # Any annotations after the last server descriptor is ignored (never provided # to the caller). while True: # skip annotations while True: pos = descriptor_file.tell() if not descriptor_file.readline().startswith(b'@'): descriptor_file.seek(pos) break if not is_bridge: descriptor_content = _read_until_keywords('router-signature', descriptor_file) # we've reached the 'router-signature', now include the pgp style block block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True) else: descriptor_content = _read_until_keywords('router-digest', descriptor_file, True) if descriptor_content: if descriptor_content[0].startswith(b'@type'): descriptor_content = descriptor_content[1:] descriptor_text = bytes.join(b'', descriptor_content) if is_bridge: if kwargs: raise ValueError( 'BUG: keyword arguments unused by bridge descriptors') yield BridgeDescriptor(descriptor_text, validate) else: yield RelayDescriptor(descriptor_text, validate, **kwargs) else: break # done parsing descriptors