def has_immutable_data(user_zonefile, data_hash): """ Does the given user have the given immutable data? Return True if so Return False if not """ assert is_user_zonefile(user_zonefile) data_hash = str(data_hash) assert scripts.is_valid_hash(data_hash), 'Invalid data hash "{}"'.format(data_hash) if 'txt' not in user_zonefile: return False for txtrec in user_zonefile['txt']: h = None try: h = get_immutable_hash_from_txt(txtrec['txt']) if h is None: continue assert scripts.is_valid_hash(h) except AssertionError as ae: log.error("Invalid immutable data hash") continue if data_hash == h: return True return False
def remove_immutable_data_zonefile(user_zonefile, data_hash): """ Remove a data hash from a user's zonefile. Return True if removed Return False if not present """ assert is_user_zonefile(user_zonefile) data_hash = str(data_hash) assert scripts.is_valid_hash(data_hash), 'Invalid data hash "{}"'.format(data_hash) if 'txt' not in user_zonefile: return False for txtrec in user_zonefile['txt']: h = None try: h = get_immutable_hash_from_txt(txtrec['txt']) if h is None: continue assert scripts.is_valid_hash(h) except AssertionError as ae: log.debug("Invalid immutable data hash") continue if data_hash == h: user_zonefile['txt'].remove(txtrec) return True return False
def put_immutable_data_zonefile(user_zonefile, data_id, data_hash, data_url=None): """ Add a data hash to a user's zonefile. Make sure it's a valid hash as well. Return True on success Return False otherwise. """ if not is_user_zonefile(user_zonefile): log.debug("Invalid zone file structure") return False data_hash = str(data_hash) assert scripts.is_valid_hash(data_hash) k = get_immutable_data_hashes(user_zonefile, data_id) if k is not None and len(k) > 0: # exists or name collision log.debug("collision on {} ({})".format(data_id, k)) return k[0] == data_hash txtrec = '#{}'.format(data_hash) if data_url is not None: txtrec = '{}{}'.format(data_url, txtrec) user_zonefile.setdefault('txt', []) name_txt = {'name': data_id, 'txt': txtrec} user_zonefile['txt'].append(name_txt) return True
def has_immutable_data_id(user_zonefile, data_id): """ Does the given user have the given immutable data? Return True if so Return False if not """ if not is_user_zonefile(user_zonefile): log.debug("Not a valid zone file") return False if 'txt' not in user_zonefile: return False for txtrec in user_zonefile['txt']: d_id = None try: d_id = txtrec['name'] h = get_immutable_hash_from_txt(txtrec['txt']) if h is None: continue assert scripts.is_valid_hash(h) except AssertionError: continue if data_id == d_id: return True return False
def get_immutable_data_url(user_zonefile, data_hash): """ Given the hash of an immutable datum, find the associated URL hint (if given) Return None if not given, or not found. """ assert is_user_zonefile(user_zonefile) if 'txt' not in user_zonefile: return None for txtrec in user_zonefile['txt']: h = None try: h = get_immutable_hash_from_txt(txtrec['txt']) if h is None: continue assert scripts.is_valid_hash(h) if data_hash != h: continue url = get_immutable_url_from_txt(txtrec['txt']) except AssertionError as ae: log.debug("Invalid immutable data hash {}".format(h)) continue return url return None
def get_immutable_hash_from_txt(txtrec): """ Given an immutable data txt record, get the hash. The hash is the suffix that begins with #. Return None if invalid or not present """ if '#' not in txtrec: return None h = txtrec.split('#')[-1] if not scripts.is_valid_hash(h): return None return h
def blockstack_immutable_data_url(blockchain_id, data_id, data_hash): """ Make a blockstack:// URL for immutable data data_id must be url-quoted """ assert re.match(schemas.OP_URLENCODED_PATTERN, data_id) if data_hash is not None and not is_valid_hash(data_hash): raise ValueError('Invalid hash: {}'.format(data_hash)) if data_hash is not None: return 'blockstack://{}.{}/#{}'.format(data_id, urllib.quote(blockchain_id), data_hash) return 'blockstack://{}.{}'.format(data_id, urllib.quote(blockchain_id))
def blockstack_immutable_data_url_parse(url): """ Parse a blockstack:// URL for immutable data Return (blockchain ID, data ID, data hash) * The hash may be None if not given, in which case, the hash should be looked up from the blockchain ID's profile. * The data ID may be None, in which case, the list of immutable data is requested. Raise on bad data """ url = str(url) immutable_data_regex = r'^blockstack://({}+)\.({}+)\.({}+)[/]*([/]+#[a-fA-F0-9]+)?$'.format( URLENCODED_CLASS, B40_NO_PERIOD_CLASS, B40_NO_PERIOD_CLASS) immutable_listing_regex = r'^blockstack://({}+)[/]+#immutable$'.format( B40_CLASS) m = re.match(immutable_data_regex, url) if m: data_id, blockchain_name, namespace_id, data_hash = m.groups() blockchain_id = '{}.{}'.format(blockchain_name, namespace_id) if not is_name_valid(blockchain_id): log.debug('Invalid blockstack ID "{}"'.format(blockchain_id)) raise ValueError('Invalid blockstack ID') if data_hash is not None: data_hash = data_hash.lower().strip('#/') if not is_valid_hash(data_hash): log.debug('Invalid data hash "{}"'.format(data_hash)) raise ValueError('Invalid data hash') return urllib.unquote(blockchain_id), data_id, data_hash else: # maybe a listing? m = re.match(immutable_listing_regex, url) if not m: log.debug('Invalid immutable URL "{}"'.format(url)) raise ValueError('Invalid immutable URL') blockchain_id = m.groups()[0] return urllib.unquote(blockchain_id), None, None return None, None, None
def list_immutable_data(user_zonefile): """ Get the IDs and hashes of all immutable data Return [(data ID, hash)] """ assert is_user_zonefile(user_zonefile) ret = [] if 'txt' not in user_zonefile: return ret for txtrec in user_zonefile['txt']: try: d_id = txtrec['name'] h = get_immutable_hash_from_txt(txtrec['txt']) assert scripts.is_valid_hash(h) ret.append((d_id, h)) except AssertionError as ae: log.error("Invalid immutable data hash") continue return ret
def get_immutable_data_hashes(user_zonefile, data_id): """ Get the hash of an immutable datum by name. Return None if there is no match. Return the list of hashes otherwise """ assert is_user_zonefile(user_zonefile) if 'txt' not in user_zonefile: return None ret = None for txtrec in user_zonefile['txt']: h, d_id = None, None try: d_id = txtrec['name'] if data_id != d_id: continue h = get_immutable_hash_from_txt(txtrec['txt']) if h is None: continue msg = 'Invalid data hash for "{}" (got "{}" from {})' assert scripts.is_valid_hash(h), msg.format(d_id, h, txtrec['txt']) except AssertionError as ae: if BLOCKSTACK_TEST is not None: log.exception(ae) continue if ret is None: ret = [h] else: ret.append(h) return ret