def request_command_list(self, jid=None): _task = yield if jid is None: jid = self._client.get_bound_jid().bare response = yield get_disco_request(Namespace.DISCO_ITEMS, jid, node=Namespace.COMMANDS) if response.isError(): raise StanzaError(response) payload = response.getQueryPayload() if payload is None: raise MalformedStanzaError('query payload missing', response) command_list = [] for item in payload: if item.getName() != 'item': continue try: command_list.append(AdHocCommand(**item.getAttrs())) except Exception as error: raise MalformedStanzaError(f'invalid item attributes: {error}', response) yield command_list
def make_query(self, jid, queryid=None, start=None, end=None, with_=None, after=None, max_=70): _task = yield response = yield _make_request(jid, queryid, start, end, with_, after, max_) if response.isError(): raise StanzaError(response) jid = response.getFrom() fin = response.getTag('fin', namespace=Namespace.MAM_2) if fin is None: raise MalformedStanzaError('fin node missing', response) rsm = parse_rsm(fin) if rsm is None: raise MalformedStanzaError('rsm set missing', response) complete = fin.getAttr('complete') == 'true' if not complete: if rsm.first is None or rsm.last is None: raise MalformedStanzaError('first or last element missing', response) yield MAMQueryData(jid=jid, complete=complete, rsm=rsm)
def request_slot(self, jid, filename, size, content_type): _task = yield response = yield _make_request(jid, filename, size, content_type) if response.isError(): raise HTTPUploadStanzaError(response) slot = response.getTag('slot', namespace=Namespace.HTTPUPLOAD_0) if slot is None: raise MalformedStanzaError('slot node missing', response) put_uri = slot.getTagAttr('put', 'url') if put_uri is None: raise MalformedStanzaError('put uri missing', response) get_uri = slot.getTagAttr('get', 'url') if get_uri is None: raise MalformedStanzaError('get uri missing', response) headers = {} for header in slot.getTag('put').getTags('header'): name = header.getAttr('name') if name not in ALLOWED_HEADERS: raise MalformedStanzaError( 'not allowed header found: %s' % name, response) data = header.getData() if '\n' in data: raise MalformedStanzaError('newline in header data found', response) headers[name] = data yield HTTPUploadData(put_uri=put_uri, get_uri=get_uri, headers=headers)
def request_preferences(self): _task = yield response = yield _make_pref_request() if response.isError(): raise StanzaError(response) prefs = response.getTag('prefs', namespace=Namespace.MAM_2) if prefs is None: raise MalformedStanzaError('prefs node missing', response) default = prefs.getAttr('default') if default is None: raise MalformedStanzaError('default attr missing', response) always_node = prefs.getTag('always') if always_node is None: raise MalformedStanzaError('always node missing', response) always = _get_preference_jids(always_node) never_node = prefs.getTag('never') if never_node is None: raise MalformedStanzaError('never node missing', response) never = _get_preference_jids(never_node) yield MAMPreferencesData(default=default, always=always, never=never)
def parse_disco_info(stanza, timestamp=None): idenities = [] features = [] dataforms = [] if timestamp is None: timestamp = time.time() query = stanza.getQuery() for node in query.getTags('identity'): attrs = node.getAttrs() try: idenities.append( DiscoIdentity(category=attrs['category'], type=attrs['type'], name=attrs.get('name'), lang=attrs.get('xml:lang'))) except Exception: raise MalformedStanzaError('invalid attributes', stanza) for node in query.getTags('feature'): try: features.append(node.getAttr('var')) except Exception: raise MalformedStanzaError('invalid attributes', stanza) for node in query.getTags('x', namespace=Namespace.DATA): dataforms.append(extend_form(node)) return DiscoInfo(stanza=stanza, identities=idenities, features=features, dataforms=dataforms, timestamp=timestamp)
def _get_published_item_id(response, node, id_): pubsub = response.getTag('pubsub', namespace=Namespace.PUBSUB) if pubsub is None: # https://xmpp.org/extensions/xep-0060.html#publisher-publish-success # If the publish request did not include an ItemID, # the IQ-result SHOULD include an empty <item/> element # that specifies the ItemID of the published item. # # If the server did not add a payload we assume the item was # published with the id we requested return id_ publish = pubsub.getTag('publish') if publish is None: raise MalformedStanzaError('publish node missing', response) if node != publish.getAttr('node'): raise MalformedStanzaError('invalid node attribute', response) item = publish.getTag('item') if item is None: raise MalformedStanzaError('item node missing', response) item_id = item.getAttr('id') if id_ is not None and item_id != id_: raise MalformedStanzaError('invalid item id', response) return item_id
def set_search(self, jid, dataform, items_per_page=50, after=None): _task = yield response = yield _make_search_query(jid, dataform, items_per_page, after) if response.isError(): raise StanzaError(response) result = response.getTag('result', namespace=Namespace.MUCLUMBUS) if result is None: raise MalformedStanzaError('result node missing', response) items = result.getTags('item') if not items: yield MuclumbusResult(first=None, last=None, max=None, end=True, items=[]) set_ = result.getTag('set', namespace=Namespace.RSM) if set_ is None: raise MalformedStanzaError('set node missing', response) first = set_.getTagData('first') last = set_.getTagData('last') try: max_ = int(set_.getTagData('max')) except Exception: raise MalformedStanzaError('invalid max value', response) results = [] for item in items: jid = item.getAttr('address') name = item.getTagData('name') nusers = item.getTagData('nusers') description = item.getTagData('description') language = item.getTagData('language') is_open = item.getTag('is-open') is not None try: anonymity_mode = AnonymityMode( item.getTagData('anonymity-mode')) except ValueError: anonymity_mode = AnonymityMode.UNKNOWN results.append( MuclumbusItem(jid=jid, name=name or '', nusers=nusers or '', description=description or '', language=language or '', is_open=is_open, anonymity_mode=anonymity_mode)) yield MuclumbusResult(first=first, last=last, max=max_, end=len(items) < max_, items=results)
def _get_vcard(item): vcard = item.getTag('vcard', namespace=Namespace.VCARD4) if vcard is None: raise MalformedStanzaError('vcard node missing', item) try: vcard = VCard.from_node(vcard) except Exception as error: raise MalformedStanzaError('invalid vcard: %s' % error, item) return vcard
def _get_pubsub_items(response, node): pubsub_node = response.getTag('pubsub', namespace=Namespace.PUBSUB) if pubsub_node is None: raise MalformedStanzaError('pubsub node missing', response) items_node = pubsub_node.getTag('items') if items_node is None: raise MalformedStanzaError('items node missing', response) if items_node.getAttr('node') != node: raise MalformedStanzaError('invalid node attr', response) return items_node.getTags('item')
def _get_pubsub_item(response, node, id_): items = _get_pubsub_items(response, node) if len(items) > 1: raise MalformedStanzaError('multiple items found', response) if not items: return None item = items[0] if item.getAttr('id') != id_: raise MalformedStanzaError('invalid item id', response) return item
def _parse_devicelist(item): ''' <items node='eu.siacs.conversations.axolotl.devicelist'> <item id='current'> <list xmlns='eu.siacs.conversations.axolotl'> <device id='12345' /> <device id='4223' /> </list> </item> </items> ''' list_node = item.getTag('list', namespace=Namespace.OMEMO_TEMP) if list_node is None: raise MalformedStanzaError('No list node found', item) if not list_node.getChildren(): return [] result = [] devices_nodes = list_node.getChildren() for dn in devices_nodes: _id = dn.getAttr('id') if _id: result.append(int(_id)) return result
def _parse_info(stanza): try: name = stanza.getQueryChild('name').getData() except Exception: raise MalformedStanzaError('name node missing', stanza) try: version = stanza.getQueryChild('version').getData() except Exception: raise MalformedStanzaError('version node missing', stanza) os_info = stanza.getQueryChild('os') if os_info is not None: os_info = os_info.getData() return SoftwareVersionResult(name, version, os_info)
def parse_private_bookmarks(response, log): query = response.getQuery() storage_node = query.getTag('storage', namespace=Namespace.BOOKMARKS) if storage_node is None: raise MalformedStanzaError('storage node missing', response) return parse_storage_node(storage_node, log)
def request_parameters(self, jid): task = yield response = yield _make_parameter_request(jid) if response.isError(): raise StanzaError(response) search = response.getTag('search', namespace=Namespace.MUCLUMBUS) if search is None: raise MalformedStanzaError('search node missing', response) dataform = search.getTag('x', namespace=Namespace.DATA) if dataform is None: raise MalformedStanzaError('dataform node missing', response) self._log.info('Muclumbus parameters received') yield finalize(task, extend_form(node=dataform))
def _parse_response(response): query = response.getQuery() seconds = query.getAttr('seconds') try: seconds = int(seconds) except Exception: raise MalformedStanzaError('seconds attribute invalid', response) return LastActivityData(seconds=seconds, status=query.getData())
def _get_avatar_data(item, id_): data_node = item.getTag('data', namespace=Namespace.AVATAR_DATA) if data_node is None: raise MalformedStanzaError('data node missing', item) data = data_node.getData() if not data: raise MalformedStanzaError('data node empty', item) try: avatar = b64decode(data, return_type=bytes) except Exception as error: raise MalformedStanzaError(f'decoding error: {error}', item) avatar_sha = hashlib.sha1(avatar).hexdigest() if avatar_sha != id_: raise MalformedStanzaError(f'avatar does not match sha', item) return AvatarData(data=avatar, sha=avatar_sha)
def execute_command(self, command, action=None, dataform=None): _task = yield if action is None: action = AdHocAction.EXECUTE attrs = { 'node': command.node, 'xmlns': Namespace.COMMANDS, 'action': action.value } if command.sessionid is not None: attrs['sessionid'] = command.sessionid response = yield _make_command(command, attrs, dataform) if response.isError(): raise StanzaError(response) command = response.getTag('command', namespace=Namespace.COMMANDS) if command is None: raise MalformedStanzaError('command node missing', response) node = command.getAttr('node') if node is None: raise MalformedStanzaError('node attribute missing', response) sessionid = command.getAttr('sessionid') if sessionid is None: raise MalformedStanzaError('sessionid attribute missing', response) status = command.getAttr('status') if status is None: raise MalformedStanzaError('status attribute missing', response) if status not in ('executing', 'completed', 'canceled'): raise MalformedStanzaError('invalid status attribute %s' % status, response) status = AdHocStatus(status) try: notes = _parse_notes(command) except ValueError as error: raise MalformedStanzaError(error, response) try: actions, default = _parse_actions(command) except ValueError as error: raise MalformedStanzaError(error, response) yield AdHocCommand(jid=response.getFrom(), name=None, node=node, sessionid=sessionid, status=status, data=command.getTag('x', namespace=Namespace.DATA), actions=actions, default=default, notes=notes)
def _get_configure_form(response, node): pubsub = response.getTag('pubsub', namespace=Namespace.PUBSUB_OWNER) if pubsub is None: raise MalformedStanzaError('pubsub node missing', response) configure = pubsub.getTag('configure') if configure is None: raise MalformedStanzaError('configure node missing', response) if node != configure.getAttr('node'): raise MalformedStanzaError('invalid node attribute', response) forms = configure.getTags('x', namespace=Namespace.DATA) for form in forms: dataform = extend_form(node=form) form_type = dataform.vars.get('FORM_TYPE') if form_type is None or form_type.value != Namespace.PUBSUB_CONFIG: continue return dataform raise MalformedStanzaError('no valid form type found', response)
def _parse_response(response): time_ = response.getTag('time') if not time_: raise MalformedStanzaError('time node missing', response) tzo = time_.getTagData('tzo') if not tzo: raise MalformedStanzaError('tzo node or data missing', response) remote_tz = create_tzinfo(tz_string=tzo) if remote_tz is None: raise MalformedStanzaError('invalid tzo data', response) utc_time = time_.getTagData('utc') if not utc_time: raise MalformedStanzaError('utc node or data missing', response) date_time = parse_datetime(utc_time, check_utc=True) if date_time is None: raise MalformedStanzaError('invalid timezone definition', response) date_time = date_time.astimezone(remote_tz) return date_time.strftime('%c %Z')
def parse_bookmark(item): conference = item.getTag('conference', namespace=Namespace.BOOKMARKS_1) if conference is None: raise MalformedStanzaError('conference node missing', item) try: jid = JID.from_string(item.getAttr('id')) except Exception as error: raise MalformedStanzaError('invalid jid: %s' % error, item) if jid.localpart is None or jid.resource is not None: raise MalformedStanzaError('invalid jid', item) autojoin = parse_autojoin(conference.getAttr('autojoin')) nick = parse_nickname(conference.getTagData('nick')) name = conference.getAttr('name') or None password = conference.getTagData('password') or None return BookmarkData(jid=jid, name=name, autojoin=autojoin, password=password, nick=nick)
def request_secret_key(self): task = yield items = yield self.request_items(Namespace.OPENPGP_SK, max_items=1) raise_if_error(items) if not items: yield task.set_result(None) try: secret_key = _parse_secret_key(items[0]) except ValueError as error: raise MalformedStanzaError(str(error), items) yield secret_key
def parse_disco_items(stanza): items = [] query = stanza.getQuery() for node in query.getTags('item'): attrs = node.getAttrs() try: items.append( DiscoItem(jid=attrs['jid'], name=attrs.get('name'), node=attrs.get('node'))) except Exception: raise MalformedStanzaError('invalid attributes', stanza) return DiscoItems(jid=stanza.getFrom(), node=query.getAttr('node'), items=items)
def request_public_key(self, jid, fingerprint): task = yield items = yield self.request_items( f'{Namespace.OPENPGP_PK}:{fingerprint}', max_items=1, jid=jid) raise_if_error(items) if not items: yield task.set_result(None) try: key = _parse_public_key(jid, items[0]) except ValueError as error: raise MalformedStanzaError(str(error), items) yield key
def _parse_register_data(response): query = response.getTag('query', namespace=Namespace.REGISTER) if query is None: raise StanzaError(response) instructions = query.getTagData('instructions') or None data = RegisterData(instructions=instructions, form=_parse_form(response), fields_form=_parse_fields_form(query), oob_url=_parse_oob_url(query), bob_data=parse_bob_data(query)) if (data.form is None and data.fields_form is None and data.oob_url is None): raise MalformedStanzaError('invalid register response', response) return data
def request_keylist(self, jid=None): task = yield items = yield self.request_items(Namespace.OPENPGP_PK, max_items=1, jid=jid) raise_if_error(items) if not items: yield task.set_result(None) try: keylist = _parse_keylist(jid, items[0]) except ValueError as error: raise MalformedStanzaError(str(error), items) self._log.info('Received keylist: %s', keylist) yield keylist
def request_avatar_metadata(self, jid=None): task = yield items = yield self.request_items(Namespace.AVATAR_METADATA, max_items=1, jid=jid) raise_if_error(items) if not items: yield task.set_result(None) item = items[0] metadata = item.getTag('metadata', namespace=Namespace.AVATAR_METADATA) if metadata is None: raise MalformedStanzaError('metadata node missing', item) if not metadata.getChildren(): yield task.set_result(None) yield AvatarMetaData.from_node(metadata, item.getAttr('id'))
def _parse_push(self, stanza, ver_support): query = stanza.getTag('query', namespace=Namespace.ROSTER) version = None if ver_support: version = query.getAttr('ver') if version is None: raise MalformedStanzaError('ver attribute missing', stanza) pushed_items = [] for item in query.getTags('item'): try: roster_item = RosterItem.from_node(item) except Exception: self._log.warning('Invalid roster item') self._log.warning(stanza) continue pushed_items.append(roster_item) return pushed_items, version
def request_blocking_list(self): _task = yield result = yield _make_blocking_list_request() if result.isError(): raise StanzaError(result) blocklist = result.getTag('blocklist', namespace=Namespace.BLOCKING) if blocklist is None: raise MalformedStanzaError('blocklist node missing', result) blocked = set() for item in blocklist.getTags('item'): try: jid = JID.from_string(item.getAttr('jid')) except Exception: self._log.info('Invalid JID: %s', item.getAttr('jid')) continue blocked.add(jid) self._log.info('Received blocking list: %s', blocked) yield blocked
def request_roster(self, version=None): _task = yield ver_support = self._client.features.has_roster_version() if not ver_support: version = None if ver_support and version is None: version = '' self._log.info('Roster versioning supported: %s', ver_support) response = yield _make_request(version, ver_support) if response.isError(): raise StanzaError(response) query = response.getTag('query', namespace=Namespace.ROSTER) if query is None: if not ver_support: raise MalformedStanzaError('query node missing', response) yield RosterData(None, version) pushed_items, version = self._parse_push(response, ver_support) yield RosterData(pushed_items, version)
def _get_vcard_node(response): vcard_node = response.getTag('vCard', namespace=Namespace.VCARD) if vcard_node is None: raise MalformedStanzaError('vCard node missing', response) return vcard_node