def _address(self, cid=None, e=None, n=None): if cid and not (e and n): e, n = ExtractEmailAndName(self.idx.EMAILS[int(cid, 36)]) vcard = self.session.config.vcards.get_vcard(e) if vcard and '@' in n: n = vcard.fn return AddressInfo(e, n, vcard=vcard)
def _index_addresses(self, cfg, terms, vcard_addresses): existing = dict([(k['address'].lower(), k) for k in vcard_addresses]) index = self._idx() # Figure out which tags are invisible so we can skip messages marked # with those tags. invisible = set([t._key for t in cfg.get_tags(flag_hides=True)]) # 1st, go through the last 1000 or so messages in the index and search # for matching senders or recipients, give medium priority. matches = {} addresses = [] for msg_idx in xrange(max(0, len(index.INDEX) - 2500), len(index.INDEX)): msg_info = index.get_msg_at_idx_pos(msg_idx) tags = set(msg_info[index.MSG_TAGS].split(',')) frm = msg_info[index.MSG_FROM] match = not (tags & invisible) if match: for term in terms: if term not in frm.lower(): match = False if match: matches[frm] = matches.get(frm, 0) + 1 if len(matches) > 1000: break # FIXME: 2nd, search the social graph for matches, give low priority. for frm in index.EMAILS: match = True for term in terms: if term not in frm.lower(): match = False if match: matches[frm] = matches.get(frm, 0) + 1 # Assign info & scores! for frm in matches: email, fn = ExtractEmailAndName(frm) boost = min(10, matches[frm]) for term in terms: boost += self._boost_rank(term, fn, email) if not email or '@' not in email: # FIXME: This may not be the right thing for alternate # message transports. pass elif email.lower() in existing: existing[email.lower()]['rank'] += min(20, boost) else: info = AddressInfo(email, fn) existing[email.lower()] = info addresses.append(info) return addresses
def email_at(i): for j in range(0, len(g)): if g[j][:1] == '(' and g[j][-1:] == ')': g[j] = g[j][1:-1] rest = ' '.join([ g[j] for j in range(0, len(g)) if (j != i) and g[j] ]).replace(' ,', ',').replace(' ;', ';') email, keys = g[i], None if '#' in email[email.index('@'):]: email, key = email.rsplit('#', 1) keys = [{'fingerprint': key}] return AddressInfo(email, rest.strip(), keys=keys)
def _vcard_addresses(self, cfg, terms): addresses = {} for vcard in cfg.vcards.find_vcards(terms, kinds='individual'): fn = vcard.get('fn') for email_vcl in vcard.get_all('email'): info = addresses.get(email_vcl.value) or {} info.update(AddressInfo(email_vcl.value, fn.value, vcard=vcard)) addresses[email_vcl.value] = info for term in terms: info['rank'] += self._boost_rank(term, fn.value, email_vcl.value) return addresses.values()
def _vcard_addresses(self, cfg, terms, ignored_count, deadline): addresses = {} for vcard in cfg.vcards.find_vcards(terms, kinds=VCardStore.KINDS_PEOPLE): fn = vcard.get('fn') for email_vcl in vcard.get_all('email'): info = addresses.get(email_vcl.value) or {} info.update(AddressInfo(email_vcl.value, fn.value, vcard=vcard)) addresses[email_vcl.value] = info for term in terms: info['rank'] += self._boost_rank(term, fn.value, email_vcl.value) if len(addresses) and time.time() > deadline: break return addresses.values()
def _normalize_key(session, key_info): """Make sure expected attributes are on all keys""" if not key_info.get("uids"): key_info["uids"] = [{"name": "", "email": "", "comment": ""}] if key_info.get("vcards") is None: key_info["vcards"] = {} for uid in key_info["uids"]: uid["name"] = uid.get("name", _('Anonymous')) uid["email"] = e = uid.get("email", '') uid["comment"] = uid.get("comment", '') if e and e not in key_info["vcards"]: vcard = session.config.vcards.get_vcard(e) if vcard: ai = AddressInfo(e, uid["name"], vcard=vcard) key_info["vcards"][e] = ai for key, default in [('on_keychain', False), ('keysize', '0'), ('keytype_name', 'unknown'), ('created', '1970-01-01 00:00:00'), ('fingerprint', 'FINGERPRINT_IS_MISSING'), ('validity', '')]: if key not in key_info: key_info[key] = default
def _normalize_key(session, key_info): """Make sure expected attributes are on all keys""" if not key_info.uids: key_info.uids.append(KeyUID()) for uid in key_info.uids: uid.name = uid.name or _('Anonymous') e = uid.email if e and e not in key_info.vcards: vcard = session.config.vcards.get_vcard(e) if vcard: ai = AddressInfo(e, uid.name, vcard=vcard) key_info.vcards[e] = ai if vcard.pgp_key == key_info.fingerprint: key_info.is_preferred = True if vcard.pgp_key_pinned: key_info.is_pinned = True key_info.origins = list(set(key_info.origins)) if not key_info.is_pinned: key_info.is_pinned = False if not key_info.is_preferred: key_info.is_preferred = False if not key_info.is_autocrypt: key_info.is_autocrypt = False
def _index_addresses(self, cfg, terms, vcard_addresses, count, deadline): existing = dict([(k['address'].lower(), k) for k in vcard_addresses]) index = self._idx() # Figure out which tags are invisible so we can skip messages marked # with those tags. invisible = set([t._key for t in cfg.get_tags(flag_hides=True)]) matches = {} addresses = [] # 1st, search the social graph for matches, give low priority. for frm in index.EMAILS: frm_lower = frm.lower() match = True for term in terms: if term not in frm_lower: match = False break if match: matches[frm] = matches.get(frm, 0) + 3 if len(matches) > (count * 10): break elif len(matches) and time.time() > deadline: break # 2nd, go through at most the last 5000 messages in the index and # search for matching senders or recipients, give medium priority. # Note: This is more CPU intensive, so we do this last. if len(matches) < (count * 5): for msg_idx in xrange(max(0, len(index.INDEX)-5000), len(index.INDEX)): msg_info = index.get_msg_at_idx_pos(msg_idx) tags = set(msg_info[index.MSG_TAGS].split(',')) match = not (tags & invisible) if match: frm = msg_info[index.MSG_FROM] search = (frm + ' ' + msg_info[index.MSG_SUBJECT]).lower() for term in terms: if term not in search: match = False break if match: matches[frm] = matches.get(frm, 0) + 1 if len(matches) > (count * 5): break if len(matches) and time.time() > deadline: break # Assign info & scores! for frm in matches: email, fn = ExtractEmailAndName(frm) boost = min(10, matches[frm]) for term in terms: boost += self._boost_rank(4, term, fn, email) if not email or '@' not in email: # FIXME: This may not be the right thing for alternate # message transports. pass elif email.lower() in existing: existing[email.lower()]['rank'] += boost else: info = AddressInfo(email, fn) info['rank'] = info.get('rank', 0) + boost existing[email.lower()] = info addresses.append(info) return addresses
def crypto_policy(cls, session, idx, emails): config = session.config for i in range(0, len(emails)): if '<' in emails[i]: emails[i] = (emails[i].split('<', 1)[1] .split('>', 1)[0] .rsplit('#', 1)[0].strip()) policies = [cls._vcard_policy(config, e) for e in set(emails)] default = [(v, k, e, p, f) for v, k, e, p, f in policies if k == 'profile'] default = default[0] if default else (None, None, None, 'best-effort', 'send_keys') cpolicy = default[-2] cformat = default[-1] # Try and merge all the user policies into one. This may lead # to conflicts which cannot be resolved. policy = cpolicy reason = None for vc, kind, email, cpol, cfmt in policies: if cpol and cpol not in ('default', 'best-effort'): if policy in ('default', 'best-effort'): policy = cpol elif policy != cpol: reason = _('Recipients have conflicting encryption ' 'policies.') policy = 'conflict' if policy == 'default': policy = 'best-effort' if not reason: reason = _('The encryption policy for these recipients is: %s' ) % policy # If we don't have a key ourselves, that limits our options... if default[0]: if default[0].get_all('KEY'): can_sign = True can_encrypt = None # not False and not True else: can_sign = False can_encrypt = False if policy in ('sign', 'sign-encrypt', 'encrypt'): reason = _('This account does not have an encryption key.') policy = 'conflict' elif policy in ('default', 'best-effort'): reason = _('This account does not have an encryption key.') policy = 'none' else: can_sign = False can_encrypt = False if can_encrypt is not False: can_encrypt = len([1 for v, k, e, p, f in policies if not v or not v.get_all('KEY')]) == 0 if not can_encrypt and 'encrypt' in policy: policy = 'conflict' if 'encrypt' in cpolicy: reason = _('Your policy is to always encrypt, ' 'but we do not have keys for everyone!') else: reason = _('Some recipients require encryption, ' 'but we do not have keys for everyone!') # If the policy is "best-effort", then we would like to sign and # encrypt if possible/safe. The bar for signing is lower. if policy == 'best-effort': should_encrypt = can_encrypt if should_encrypt: for v, k, e, p, f in policies: if k and k == 'profile': pass elif cls._encryption_ratio(session, idx, e) < 0.8: should_encrypt = False break if should_encrypt: policy = 'sign-encrypt' reason = _('We have keys for everyone!') if can_sign and not should_encrypt: # FIXME: Should we check if anyone is using a lame MUA? policy = 'sign' if can_encrypt: reason = _('Will not encrypt because ' 'historic data is insufficient.') else: reason = _('Cannot encrypt because we ' 'do not have keys for all recipients.') if 'send_keys' in cformat: send_keys = cls.ShouldAttachKey( config, vcards=[p[0] for p in policies], emails=[p[2] for p in policies if not p[0]]) else: send_keys = False return { 'reason': reason, 'can-sign': can_sign, 'can-encrypt': can_encrypt, 'crypto-policy': policy, 'crypto-format': cformat, 'send-keys': send_keys, 'addresses': dict([(e, AddressInfo(e, vc.fn if vc else e, vcard=vc)) for vc, k, e, p, f in policies if vc]) }
def _index_addresses(self, cfg, terms, vcard_addresses, count, deadline): existing = dict([(k['address'].lower(), k) for k in vcard_addresses]) index = self._idx() # Figure out which tags are invisible so we can skip messages marked # with those tags. invisible = set([t._key for t in cfg.get_tags(flag_hides=True)]) matches = {} addresses = [] # 1st, search the social graph for matches, give low priority. for frm in index.EMAILS: frm_lower = frm.lower() match = True for term in terms: if term not in frm_lower: match = False break if match: matches[frm] = matches.get(frm, 0) + 3 if len(matches) > (count * 10): break elif len(matches) and time.time() > deadline: break # 2nd, go through at most the last 5000 messages in the index and # search for matching senders or recipients, give medium priority. # Note: This is more CPU intensive, so we do this last. if len(matches) < (count * 5): for msg_idx in xrange(max(0, len(index.INDEX) - 5000), len(index.INDEX)): msg_info = index.get_msg_at_idx_pos(msg_idx) tags = set(msg_info[index.MSG_TAGS].split(',')) match = not (tags & invisible) if match: frm = msg_info[index.MSG_FROM] search = (frm + ' ' + msg_info[index.MSG_SUBJECT]).lower() for term in terms: if term not in search: match = False break if match: matches[frm] = matches.get(frm, 0) + 1 if len(matches) > (count * 5): break if len(matches) and time.time() > deadline: break # Assign info & scores! for frm in matches: email, fn = ExtractEmailAndName(frm) boost = min(10, matches[frm]) for term in terms: boost += self._boost_rank(term, fn, email) if not email or '@' not in email: # FIXME: This may not be the right thing for alternate # message transports. pass elif email.lower() in existing: existing[email.lower()]['rank'] += min(20, boost) else: info = AddressInfo(email, fn) existing[email.lower()] = info addresses.append(info) return addresses
def _address(self, cid): e, n = ExtractEmailAndName(self.idx.EMAILS[int(cid, 36)]) vcard = self.session.config.vcards.get_vcard(e) return AddressInfo(e, n, vcard=vcard)
def _create_from_to_cc(cls, idx, session, trees): config = session.config ahp = AddressHeaderParser() ref_from, ref_to, ref_cc = [], [], [] result = {'from': '', 'to': [], 'cc': []} def merge_contact(ai): vcard = session.config.vcards.get_vcard(ai.address) if vcard: ai.merge_vcard(vcard) return ai # Parse the headers, so we know what we're working with. We prune # some of the duplicates at this stage. for addrs in [t['addresses'] for t in trees]: alist = [] for dst, addresses in ((ref_from, addrs.get('reply-to') or addrs.get('from', [])), (ref_to, addrs.get('to', [])), (ref_cc, addrs.get('cc', []))): alist += [d.address for d in dst] dst.extend([a for a in addresses if a.address not in alist]) # 1st, choose a from address. We'll use the system default if # nothing is found, but hopefully we'll find an address we # recognize in one of the headers. from_address = (session.config.prefs.default_email or session.config.profiles[0].email) profile_emails = [p.email for p in session.config.profiles if p.email] for src in (ref_from, ref_to, ref_cc): matches = [s for s in src if s.address in profile_emails] if matches: from_address = matches[0].address break result['from'] = ahp.normalized(addresses=[ AddressInfo(p.email, p.name) for p in session.config.profiles if p.email == from_address ], force_name=True) def addresses(addrs, exclude=[]): alist = [from_address] + [a.address for a in exclude] return ahp.normalized_addresses(addresses=[ merge_contact(a) for a in addrs if a.address not in alist and not a.address.startswith( 'noreply@') and '@noreply' not in a.address ], with_keys=True, force_name=True) # If only replying to messages sent from chosen from, then this is # a follow-up or clarification, so just use the same headers. if len([e for e in ref_from if e.address == from_address]) == len(ref_from): if ref_to: result['to'] = addresses(ref_to) if ref_cc: result['cc'] = addresses(ref_cc) # Else, if replying to other people: # - Construct To from the From lines, excluding own from # - Construct Cc from the To and CC lines, except new To/From else: result['to'] = addresses(ref_from) result['cc'] = addresses(ref_to + ref_cc, exclude=ref_from) return result