3 from zope.interface import implements
4 from twisted.python.components import registerAdapter
5 from allmydata.storage.server import si_a2b, si_b2a
6 from allmydata.util import base32, hashutil
7 from allmydata.interfaces import IURI, IDirnodeURI, IFileURI, IImmutableFileURI, \
8 IVerifierURI, IMutableFileURI, IDirectoryURI, IReadonlyDirectoryURI, \
9 MustBeDeepImmutableError, MustBeReadonlyError, CapConstraintError
11 class BadURIError(CapConstraintError):
14 # The URI shall be an ASCII representation of a reference to the file/directory.
15 # It shall contain enough information to retrieve and validate the contents.
16 # It shall be expressed in a limited character set (currently base32 plus ':' and
17 # capital letters, but future URIs might use a larger charset).
20 # - rename all of the *URI classes/interfaces to *Cap
21 # - make variable and method names consistently use _uri for an URI string,
22 # and _cap for a Cap object (decoded URI)
23 # - remove the human_encoding methods?
25 BASE32STR_128bits = '(%s{25}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_3bits)
26 BASE32STR_256bits = '(%s{51}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_1bits)
30 NUMBER_IGNORE='(?:[0-9]+)'
32 # "human-encoded" URIs are allowed to come with a leading
33 # 'http://127.0.0.1:(8123|3456)/uri/' that will be ignored.
34 # Note that nothing in the Tahoe code currently uses the human encoding.
35 OPTIONALHTTPLEAD=r'(?:https?://(?:[^:/]+)(?::%s)?/uri/)?' % NUMBER_IGNORE
40 return self.to_string().__hash__()
42 def __eq__(self, them):
43 if isinstance(them, _BaseURI):
44 return self.to_string() == them.to_string()
48 def __ne__(self, them):
49 if isinstance(them, _BaseURI):
50 return self.to_string() != them.to_string()
54 def to_human_encoding(self):
55 return 'http://127.0.0.1:3456/uri/'+self.to_string()
57 def get_storage_index(self):
58 return self.storage_index
61 class CHKFileURI(_BaseURI):
62 implements(IURI, IImmutableFileURI)
64 BASE_STRING='URI:CHK:'
65 STRING_RE=re.compile('^URI:CHK:'+BASE32STR_128bits+':'+
66 BASE32STR_256bits+':'+NUMBER+':'+NUMBER+':'+NUMBER+
68 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'CHK'+SEP+
69 BASE32STR_128bits+SEP+BASE32STR_256bits+SEP+NUMBER+
70 SEP+NUMBER+SEP+NUMBER+'$')
72 def __init__(self, key, uri_extension_hash, needed_shares, total_shares,
75 self.uri_extension_hash = uri_extension_hash
76 self.needed_shares = needed_shares
77 self.total_shares = total_shares
79 self.storage_index = hashutil.storage_index_hash(self.key)
80 if not len(self.storage_index) == 16: # sha256 hash truncated to 128
81 raise BadURIError("storage index must be 16 bytes long")
84 def init_from_human_encoding(cls, uri):
85 mo = cls.HUMAN_RE.search(uri)
87 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
88 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
89 int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
92 def init_from_string(cls, uri):
93 mo = cls.STRING_RE.search(uri)
95 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
96 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
97 int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
100 assert isinstance(self.needed_shares, int)
101 assert isinstance(self.total_shares, int)
102 assert isinstance(self.size, (int,long))
104 return ('URI:CHK:%s:%s:%d:%d:%d' %
105 (base32.b2a(self.key),
106 base32.b2a(self.uri_extension_hash),
111 def is_readonly(self):
114 def is_mutable(self):
117 def get_readonly(self):
123 def get_verify_cap(self):
124 return CHKFileVerifierURI(storage_index=self.storage_index,
125 uri_extension_hash=self.uri_extension_hash,
126 needed_shares=self.needed_shares,
127 total_shares=self.total_shares,
130 class CHKFileVerifierURI(_BaseURI):
131 implements(IVerifierURI)
133 BASE_STRING='URI:CHK-Verifier:'
134 STRING_RE=re.compile('^URI:CHK-Verifier:'+BASE32STR_128bits+':'+
135 BASE32STR_256bits+':'+NUMBER+':'+NUMBER+':'+NUMBER)
136 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'CHK-Verifier'+SEP+
137 BASE32STR_128bits+SEP+BASE32STR_256bits+SEP+NUMBER+
138 SEP+NUMBER+SEP+NUMBER)
140 def __init__(self, storage_index, uri_extension_hash,
141 needed_shares, total_shares, size):
142 assert len(storage_index) == 16
143 self.storage_index = storage_index
144 self.uri_extension_hash = uri_extension_hash
145 self.needed_shares = needed_shares
146 self.total_shares = total_shares
150 def init_from_human_encoding(cls, uri):
151 mo = cls.HUMAN_RE.search(uri)
153 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
154 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
155 int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
158 def init_from_string(cls, uri):
159 mo = cls.STRING_RE.search(uri)
161 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
162 return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)),
163 int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
166 assert isinstance(self.needed_shares, int)
167 assert isinstance(self.total_shares, int)
168 assert isinstance(self.size, (int,long))
170 return ('URI:CHK-Verifier:%s:%s:%d:%d:%d' %
171 (si_b2a(self.storage_index),
172 base32.b2a(self.uri_extension_hash),
177 def is_readonly(self):
180 def is_mutable(self):
183 def get_readonly(self):
186 def get_verify_cap(self):
190 class LiteralFileURI(_BaseURI):
191 implements(IURI, IImmutableFileURI)
193 BASE_STRING='URI:LIT:'
194 STRING_RE=re.compile('^URI:LIT:'+base32.BASE32STR_anybytes+'$')
195 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'LIT'+SEP+base32.BASE32STR_anybytes+'$')
197 def __init__(self, data=None):
199 assert isinstance(data, str)
203 def init_from_human_encoding(cls, uri):
204 mo = cls.HUMAN_RE.search(uri)
206 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
207 return cls(base32.a2b(mo.group(1)))
210 def init_from_string(cls, uri):
211 mo = cls.STRING_RE.search(uri)
213 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
214 return cls(base32.a2b(mo.group(1)))
217 return 'URI:LIT:%s' % base32.b2a(self.data)
219 def is_readonly(self):
222 def is_mutable(self):
225 def get_readonly(self):
228 def get_storage_index(self):
231 def get_verify_cap(self):
232 # LIT files need no verification, all the data is present in the URI
236 return len(self.data)
239 class WriteableSSKFileURI(_BaseURI):
240 implements(IURI, IMutableFileURI)
242 BASE_STRING='URI:SSK:'
243 STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+
244 BASE32STR_256bits+'$')
245 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK'+SEP+
246 BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
248 def __init__(self, writekey, fingerprint):
249 self.writekey = writekey
250 self.readkey = hashutil.ssk_readkey_hash(writekey)
251 self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
252 assert len(self.storage_index) == 16
253 self.fingerprint = fingerprint
256 def init_from_human_encoding(cls, uri):
257 mo = cls.HUMAN_RE.search(uri)
259 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
260 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
263 def init_from_string(cls, uri):
264 mo = cls.STRING_RE.search(uri)
266 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
267 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
270 assert isinstance(self.writekey, str)
271 assert isinstance(self.fingerprint, str)
272 return 'URI:SSK:%s:%s' % (base32.b2a(self.writekey),
273 base32.b2a(self.fingerprint))
276 return "<%s %s>" % (self.__class__.__name__, self.abbrev())
279 return base32.b2a(self.writekey[:5])
282 return base32.b2a(self.storage_index)[:5]
284 def is_readonly(self):
287 def is_mutable(self):
290 def get_readonly(self):
291 return ReadonlySSKFileURI(self.readkey, self.fingerprint)
293 def get_verify_cap(self):
294 return SSKVerifierURI(self.storage_index, self.fingerprint)
297 class ReadonlySSKFileURI(_BaseURI):
298 implements(IURI, IMutableFileURI)
300 BASE_STRING='URI:SSK-RO:'
301 STRING_RE=re.compile('^URI:SSK-RO:'+BASE32STR_128bits+':'+BASE32STR_256bits+'$')
302 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK-RO'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
304 def __init__(self, readkey, fingerprint):
305 self.readkey = readkey
306 self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
307 assert len(self.storage_index) == 16
308 self.fingerprint = fingerprint
311 def init_from_human_encoding(cls, uri):
312 mo = cls.HUMAN_RE.search(uri)
314 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
315 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
318 def init_from_string(cls, uri):
319 mo = cls.STRING_RE.search(uri)
321 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
322 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
325 assert isinstance(self.readkey, str)
326 assert isinstance(self.fingerprint, str)
327 return 'URI:SSK-RO:%s:%s' % (base32.b2a(self.readkey),
328 base32.b2a(self.fingerprint))
331 return "<%s %s>" % (self.__class__.__name__, self.abbrev())
334 return base32.b2a(self.readkey[:5])
337 return base32.b2a(self.storage_index)[:5]
339 def is_readonly(self):
342 def is_mutable(self):
345 def get_readonly(self):
348 def get_verify_cap(self):
349 return SSKVerifierURI(self.storage_index, self.fingerprint)
352 class SSKVerifierURI(_BaseURI):
353 implements(IVerifierURI)
355 BASE_STRING='URI:SSK-Verifier:'
356 STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'$')
357 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK-Verifier'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
359 def __init__(self, storage_index, fingerprint):
360 assert len(storage_index) == 16
361 self.storage_index = storage_index
362 self.fingerprint = fingerprint
365 def init_from_human_encoding(cls, uri):
366 mo = cls.HUMAN_RE.search(uri)
368 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
369 return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
372 def init_from_string(cls, uri):
373 mo = cls.STRING_RE.search(uri)
375 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
376 return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
379 assert isinstance(self.storage_index, str)
380 assert isinstance(self.fingerprint, str)
381 return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index),
382 base32.b2a(self.fingerprint))
384 def is_readonly(self):
387 def is_mutable(self):
390 def get_readonly(self):
393 def get_verify_cap(self):
396 class _DirectoryBaseURI(_BaseURI):
397 implements(IURI, IDirnodeURI)
398 def __init__(self, filenode_uri=None):
399 self._filenode_uri = filenode_uri
402 return "<%s %s>" % (self.__class__.__name__, self.abbrev())
405 def init_from_string(cls, uri):
406 mo = cls.BASE_STRING_RE.search(uri)
408 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
409 bits = uri[mo.end():]
410 fn = cls.INNER_URI_CLASS.init_from_string(
411 cls.INNER_URI_CLASS.BASE_STRING+bits)
415 def init_from_human_encoding(cls, uri):
416 mo = cls.BASE_HUMAN_RE.search(uri)
418 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
419 bits = uri[mo.end():]
420 while bits and bits[-1] == '/':
422 fn = cls.INNER_URI_CLASS.init_from_string(
423 cls.INNER_URI_CLASS.BASE_STRING+urllib.unquote(bits))
427 fnuri = self._filenode_uri.to_string()
428 mo = re.match(self.INNER_URI_CLASS.BASE_STRING, fnuri)
430 bits = fnuri[mo.end():]
431 return self.BASE_STRING+bits
434 return self._filenode_uri.to_string().split(':')[2][:5]
437 return base32.b2a(self._filenode_uri.storage_index)[:5]
439 def is_mutable(self):
442 def get_filenode_cap(self):
443 return self._filenode_uri
445 def get_verify_cap(self):
446 return DirectoryURIVerifier(self._filenode_uri.get_verify_cap())
448 def get_storage_index(self):
449 return self._filenode_uri.get_storage_index()
451 class DirectoryURI(_DirectoryBaseURI):
452 implements(IDirectoryURI)
454 BASE_STRING='URI:DIR2:'
455 BASE_STRING_RE=re.compile('^'+BASE_STRING)
456 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2'+SEP)
457 INNER_URI_CLASS=WriteableSSKFileURI
459 def __init__(self, filenode_uri=None):
461 assert not filenode_uri.is_readonly()
462 _DirectoryBaseURI.__init__(self, filenode_uri)
464 def is_readonly(self):
467 def get_readonly(self):
468 return ReadonlyDirectoryURI(self._filenode_uri.get_readonly())
471 class ReadonlyDirectoryURI(_DirectoryBaseURI):
472 implements(IReadonlyDirectoryURI)
474 BASE_STRING='URI:DIR2-RO:'
475 BASE_STRING_RE=re.compile('^'+BASE_STRING)
476 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-RO'+SEP)
477 INNER_URI_CLASS=ReadonlySSKFileURI
479 def __init__(self, filenode_uri=None):
481 assert filenode_uri.is_readonly()
482 _DirectoryBaseURI.__init__(self, filenode_uri)
484 def is_readonly(self):
487 def get_readonly(self):
491 class _ImmutableDirectoryBaseURI(_DirectoryBaseURI):
492 def __init__(self, filenode_uri=None):
494 assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri
495 assert not filenode_uri.is_mutable()
496 _DirectoryBaseURI.__init__(self, filenode_uri)
498 def is_readonly(self):
501 def is_mutable(self):
504 def get_readonly(self):
508 class ImmutableDirectoryURI(_ImmutableDirectoryBaseURI):
509 BASE_STRING='URI:DIR2-CHK:'
510 BASE_STRING_RE=re.compile('^'+BASE_STRING)
511 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK'+SEP)
512 INNER_URI_CLASS=CHKFileURI
514 def get_verify_cap(self):
515 vcap = self._filenode_uri.get_verify_cap()
516 return ImmutableDirectoryURIVerifier(vcap)
519 class LiteralDirectoryURI(_ImmutableDirectoryBaseURI):
520 BASE_STRING='URI:DIR2-LIT:'
521 BASE_STRING_RE=re.compile('^'+BASE_STRING)
522 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-LIT'+SEP)
523 INNER_URI_CLASS=LiteralFileURI
525 def get_verify_cap(self):
526 # LIT caps have no verifier, since they aren't distributed
530 def wrap_dirnode_cap(filecap):
531 if isinstance(filecap, WriteableSSKFileURI):
532 return DirectoryURI(filecap)
533 if isinstance(filecap, ReadonlySSKFileURI):
534 return ReadonlyDirectoryURI(filecap)
535 if isinstance(filecap, CHKFileURI):
536 return ImmutableDirectoryURI(filecap)
537 if isinstance(filecap, LiteralFileURI):
538 return LiteralDirectoryURI(filecap)
539 assert False, "cannot interpret as a directory cap: %s" % filecap.__class__
542 class DirectoryURIVerifier(_DirectoryBaseURI):
543 implements(IVerifierURI)
545 BASE_STRING='URI:DIR2-Verifier:'
546 BASE_STRING_RE=re.compile('^'+BASE_STRING)
547 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-Verifier'+SEP)
548 INNER_URI_CLASS=SSKVerifierURI
550 def __init__(self, filenode_uri=None):
552 assert IVerifierURI.providedBy(filenode_uri)
553 self._filenode_uri = filenode_uri
555 def get_filenode_cap(self):
556 return self._filenode_uri
558 def is_mutable(self):
562 class ImmutableDirectoryURIVerifier(DirectoryURIVerifier):
563 implements(IVerifierURI)
564 BASE_STRING='URI:DIR2-CHK-Verifier:'
565 BASE_STRING_RE=re.compile('^'+BASE_STRING)
566 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK-VERIFIER'+SEP)
567 INNER_URI_CLASS=CHKFileVerifierURI
571 def __init__(self, uri, error=None):
578 def get_readonly(self):
584 def get_verify_cap(self):
588 ALLEGED_READONLY_PREFIX = 'ro.'
589 ALLEGED_IMMUTABLE_PREFIX = 'imm.'
591 def from_string(u, deep_immutable=False, name=u"<unknown name>"):
592 if not isinstance(u, str):
593 raise TypeError("unknown URI type: %s.." % str(u)[:100])
595 # We allow and check ALLEGED_READONLY_PREFIX or ALLEGED_IMMUTABLE_PREFIX
596 # on all URIs, even though we would only strictly need to do so for caps of
597 # new formats (post Tahoe-LAFS 1.6). URIs that are not consistent with their
598 # prefix are treated as unknown. This should be revisited when we add the
599 # new cap formats. See <http://allmydata.org/trac/tahoe/ticket/833#comment:31>.
601 can_be_mutable = can_be_writeable = not deep_immutable
602 if s.startswith(ALLEGED_IMMUTABLE_PREFIX):
603 can_be_mutable = can_be_writeable = False
604 s = s[len(ALLEGED_IMMUTABLE_PREFIX):]
605 elif s.startswith(ALLEGED_READONLY_PREFIX):
606 can_be_writeable = False
607 s = s[len(ALLEGED_READONLY_PREFIX):]
612 if s.startswith('URI:CHK:'):
613 return CHKFileURI.init_from_string(s)
614 elif s.startswith('URI:CHK-Verifier:'):
615 return CHKFileVerifierURI.init_from_string(s)
616 elif s.startswith('URI:LIT:'):
617 return LiteralFileURI.init_from_string(s)
618 elif s.startswith('URI:SSK:'):
620 return WriteableSSKFileURI.init_from_string(s)
621 kind = "URI:SSK file writecap"
622 elif s.startswith('URI:SSK-RO:'):
624 return ReadonlySSKFileURI.init_from_string(s)
625 kind = "URI:SSK-RO readcap to a mutable file"
626 elif s.startswith('URI:SSK-Verifier:'):
627 return SSKVerifierURI.init_from_string(s)
628 elif s.startswith('URI:DIR2:'):
630 return DirectoryURI.init_from_string(s)
631 kind = "URI:DIR2 directory writecap"
632 elif s.startswith('URI:DIR2-RO:'):
634 return ReadonlyDirectoryURI.init_from_string(s)
635 kind = "URI:DIR2-RO readcap to a mutable directory"
636 elif s.startswith('URI:DIR2-Verifier:'):
637 return DirectoryURIVerifier.init_from_string(s)
638 elif s.startswith('URI:DIR2-CHK:'):
639 return ImmutableDirectoryURI.init_from_string(s)
640 elif s.startswith('URI:DIR2-LIT:'):
641 return LiteralDirectoryURI.init_from_string(s)
642 elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable:
643 # For testing how future writeable caps would behave in read-only contexts.
644 kind = "x-tahoe-future-test-writeable: testing cap"
645 elif s.startswith('x-tahoe-future-test-mutable:') and not can_be_mutable:
646 # For testing how future mutable readcaps would behave in immutable contexts.
647 kind = "x-tahoe-future-test-mutable: testing cap"
651 # We fell through because a constraint was not met.
652 # Prefer to report the most specific constraint.
653 if not can_be_mutable:
654 error = MustBeDeepImmutableError(kind + " used in an immutable context", name)
656 error = MustBeReadonlyError(kind + " used in a read-only context", name)
658 except BadURIError, e:
661 return UnknownURI(u, error=error)
665 from_string(s, deep_immutable=False)
667 except (TypeError, AssertionError):
670 def is_literal_file_uri(s):
671 if not isinstance(s, str):
673 return (s.startswith('URI:LIT:') or
674 s.startswith(ALLEGED_READONLY_PREFIX + 'URI:LIT:') or
675 s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:LIT:'))
677 def has_uri_prefix(s):
678 if not isinstance(s, str):
680 return (s.startswith("URI:") or
681 s.startswith(ALLEGED_READONLY_PREFIX + 'URI:') or
682 s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:'))
685 # These take the same keyword arguments as from_string above.
687 def from_string_dirnode(s, **kwargs):
688 u = from_string(s, **kwargs)
689 assert IDirnodeURI.providedBy(u)
692 registerAdapter(from_string_dirnode, str, IDirnodeURI)
694 def from_string_filenode(s, **kwargs):
695 u = from_string(s, **kwargs)
696 assert IFileURI.providedBy(u)
699 registerAdapter(from_string_filenode, str, IFileURI)
701 def from_string_mutable_filenode(s, **kwargs):
702 u = from_string(s, **kwargs)
703 assert IMutableFileURI.providedBy(u)
705 registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
707 def from_string_verifier(s, **kwargs):
708 u = from_string(s, **kwargs)
709 assert IVerifierURI.providedBy(u)
711 registerAdapter(from_string_verifier, str, IVerifierURI)
714 def pack_extension(data):
716 for k in sorted(data.keys()):
718 if isinstance(value, (int, long)):
720 assert isinstance(value, str), k
721 assert re.match(r'^[a-zA-Z_\-]+$', k)
722 pieces.append(k + ':' + hashutil.netstring(value))
723 uri_extension = ''.join(pieces)
726 def unpack_extension(data):
729 colon = data.index(':')
731 data = data[colon+1:]
733 colon = data.index(':')
734 number = data[:colon]
736 data = data[colon+1:]
738 value = data[:length]
739 assert data[length] == ','
740 data = data[length+1:]
744 # convert certain things to numbers
745 for intkey in ('size', 'segment_size', 'num_segments',
746 'needed_shares', 'total_shares'):
748 d[intkey] = int(d[intkey])
752 def unpack_extension_readable(data):
753 unpacked = unpack_extension(data)
754 unpacked["UEB_hash"] = hashutil.uri_extension_hash(data)
755 for k in sorted(unpacked.keys()):
757 unpacked[k] = base32.b2a(unpacked[k])