]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/uri.py
Additional fix for abbrev_si, with test
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / uri.py
1
2 import re, urllib
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
10
11 class BadURIError(CapConstraintError):
12     pass
13
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).
18
19 # TODO:
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?
24
25 BASE32STR_128bits = '(%s{25}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_3bits)
26 BASE32STR_256bits = '(%s{51}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_1bits)
27
28 SEP='(?::|%3A)'
29 NUMBER='([0-9]+)'
30 NUMBER_IGNORE='(?:[0-9]+)'
31
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
36
37
38 class _BaseURI:
39     def __hash__(self):
40         return self.to_string().__hash__()
41
42     def __eq__(self, them):
43         if isinstance(them, _BaseURI):
44             return self.to_string() == them.to_string()
45         else:
46             return False
47
48     def __ne__(self, them):
49         if isinstance(them, _BaseURI):
50             return self.to_string() != them.to_string()
51         else:
52             return True
53
54     def to_human_encoding(self):
55         return 'http://127.0.0.1:3456/uri/'+self.to_string()
56
57     def get_storage_index(self):
58         return self.storage_index
59
60
61 class CHKFileURI(_BaseURI):
62     implements(IURI, IImmutableFileURI)
63
64     BASE_STRING='URI:CHK:'
65     STRING_RE=re.compile('^URI:CHK:'+BASE32STR_128bits+':'+
66                          BASE32STR_256bits+':'+NUMBER+':'+NUMBER+':'+NUMBER+
67                          '$')
68     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'CHK'+SEP+
69                      BASE32STR_128bits+SEP+BASE32STR_256bits+SEP+NUMBER+
70                      SEP+NUMBER+SEP+NUMBER+'$')
71
72     def __init__(self, key, uri_extension_hash, needed_shares, total_shares,
73                  size):
74         self.key = key
75         self.uri_extension_hash = uri_extension_hash
76         self.needed_shares = needed_shares
77         self.total_shares = total_shares
78         self.size = size
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")
82
83     @classmethod
84     def init_from_human_encoding(cls, uri):
85         mo = cls.HUMAN_RE.search(uri)
86         if not mo:
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)))
90
91     @classmethod
92     def init_from_string(cls, uri):
93         mo = cls.STRING_RE.search(uri)
94         if not mo:
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)))
98
99     def to_string(self):
100         assert isinstance(self.needed_shares, int)
101         assert isinstance(self.total_shares, int)
102         assert isinstance(self.size, (int,long))
103
104         return ('URI:CHK:%s:%s:%d:%d:%d' %
105                 (base32.b2a(self.key),
106                  base32.b2a(self.uri_extension_hash),
107                  self.needed_shares,
108                  self.total_shares,
109                  self.size))
110
111     def is_readonly(self):
112         return True
113
114     def is_mutable(self):
115         return False
116
117     def get_readonly(self):
118         return self
119
120     def get_size(self):
121         return self.size
122
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,
128                                   size=self.size)
129
130 class CHKFileVerifierURI(_BaseURI):
131     implements(IVerifierURI)
132
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)
139
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
147         self.size = size
148
149     @classmethod
150     def init_from_human_encoding(cls, uri):
151         mo = cls.HUMAN_RE.search(uri)
152         if not mo:
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)))
156
157     @classmethod
158     def init_from_string(cls, uri):
159         mo = cls.STRING_RE.search(uri)
160         if not mo:
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)))
164
165     def to_string(self):
166         assert isinstance(self.needed_shares, int)
167         assert isinstance(self.total_shares, int)
168         assert isinstance(self.size, (int,long))
169
170         return ('URI:CHK-Verifier:%s:%s:%d:%d:%d' %
171                 (si_b2a(self.storage_index),
172                  base32.b2a(self.uri_extension_hash),
173                  self.needed_shares,
174                  self.total_shares,
175                  self.size))
176
177     def is_readonly(self):
178         return True
179
180     def is_mutable(self):
181         return False
182
183     def get_readonly(self):
184         return self
185
186     def get_verify_cap(self):
187         return self
188
189
190 class LiteralFileURI(_BaseURI):
191     implements(IURI, IImmutableFileURI)
192
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+'$')
196
197     def __init__(self, data=None):
198         if data is not None:
199             assert isinstance(data, str)
200             self.data = data
201
202     @classmethod
203     def init_from_human_encoding(cls, uri):
204         mo = cls.HUMAN_RE.search(uri)
205         if not mo:
206             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
207         return cls(base32.a2b(mo.group(1)))
208
209     @classmethod
210     def init_from_string(cls, uri):
211         mo = cls.STRING_RE.search(uri)
212         if not mo:
213             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
214         return cls(base32.a2b(mo.group(1)))
215
216     def to_string(self):
217         return 'URI:LIT:%s' % base32.b2a(self.data)
218
219     def is_readonly(self):
220         return True
221
222     def is_mutable(self):
223         return False
224
225     def get_readonly(self):
226         return self
227
228     def get_storage_index(self):
229         return None
230
231     def get_verify_cap(self):
232         # LIT files need no verification, all the data is present in the URI
233         return None
234
235     def get_size(self):
236         return len(self.data)
237
238
239 class WriteableSSKFileURI(_BaseURI):
240     implements(IURI, IMutableFileURI)
241
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+'$')
247
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
254
255     @classmethod
256     def init_from_human_encoding(cls, uri):
257         mo = cls.HUMAN_RE.search(uri)
258         if not mo:
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)))
261
262     @classmethod
263     def init_from_string(cls, uri):
264         mo = cls.STRING_RE.search(uri)
265         if not mo:
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)))
268
269     def to_string(self):
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))
274
275     def __repr__(self):
276         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
277
278     def abbrev(self):
279         return base32.b2a(self.writekey[:5])
280
281     def abbrev_si(self):
282         return base32.b2a(self.storage_index)[:5]
283
284     def is_readonly(self):
285         return False
286
287     def is_mutable(self):
288         return True
289
290     def get_readonly(self):
291         return ReadonlySSKFileURI(self.readkey, self.fingerprint)
292
293     def get_verify_cap(self):
294         return SSKVerifierURI(self.storage_index, self.fingerprint)
295
296
297 class ReadonlySSKFileURI(_BaseURI):
298     implements(IURI, IMutableFileURI)
299
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+'$')
303
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
309
310     @classmethod
311     def init_from_human_encoding(cls, uri):
312         mo = cls.HUMAN_RE.search(uri)
313         if not mo:
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)))
316
317     @classmethod
318     def init_from_string(cls, uri):
319         mo = cls.STRING_RE.search(uri)
320         if not mo:
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)))
323
324     def to_string(self):
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))
329
330     def __repr__(self):
331         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
332
333     def abbrev(self):
334         return base32.b2a(self.readkey[:5])
335
336     def abbrev_si(self):
337         return base32.b2a(self.storage_index)[:5]
338
339     def is_readonly(self):
340         return True
341
342     def is_mutable(self):
343         return True
344
345     def get_readonly(self):
346         return self
347
348     def get_verify_cap(self):
349         return SSKVerifierURI(self.storage_index, self.fingerprint)
350
351
352 class SSKVerifierURI(_BaseURI):
353     implements(IVerifierURI)
354
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+'$')
358
359     def __init__(self, storage_index, fingerprint):
360         assert len(storage_index) == 16
361         self.storage_index = storage_index
362         self.fingerprint = fingerprint
363
364     @classmethod
365     def init_from_human_encoding(cls, uri):
366         mo = cls.HUMAN_RE.search(uri)
367         if not mo:
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)))
370
371     @classmethod
372     def init_from_string(cls, uri):
373         mo = cls.STRING_RE.search(uri)
374         if not mo:
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)))
377
378     def to_string(self):
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))
383
384     def is_readonly(self):
385         return True
386
387     def is_mutable(self):
388         return False
389
390     def get_readonly(self):
391         return self
392
393     def get_verify_cap(self):
394         return self
395
396 class _DirectoryBaseURI(_BaseURI):
397     implements(IURI, IDirnodeURI)
398     def __init__(self, filenode_uri=None):
399         self._filenode_uri = filenode_uri
400
401     def __repr__(self):
402         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
403
404     @classmethod
405     def init_from_string(cls, uri):
406         mo = cls.BASE_STRING_RE.search(uri)
407         if not mo:
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)
412         return cls(fn)
413
414     @classmethod
415     def init_from_human_encoding(cls, uri):
416         mo = cls.BASE_HUMAN_RE.search(uri)
417         if not mo:
418             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
419         bits = uri[mo.end():]
420         while bits and bits[-1] == '/':
421             bits = bits[:-1]
422         fn = cls.INNER_URI_CLASS.init_from_string(
423             cls.INNER_URI_CLASS.BASE_STRING+urllib.unquote(bits))
424         return cls(fn)
425
426     def to_string(self):
427         fnuri = self._filenode_uri.to_string()
428         mo = re.match(self.INNER_URI_CLASS.BASE_STRING, fnuri)
429         assert mo, fnuri
430         bits = fnuri[mo.end():]
431         return self.BASE_STRING+bits
432
433     def abbrev(self):
434         return self._filenode_uri.to_string().split(':')[2][:5]
435
436     def abbrev_si(self):
437         si = self._filenode_uri.get_storage_index()
438         if si is None:
439             return "<LIT>"
440         return base32.b2a(si)[:5]
441
442     def is_mutable(self):
443         return True
444
445     def get_filenode_cap(self):
446         return self._filenode_uri
447
448     def get_verify_cap(self):
449         return DirectoryURIVerifier(self._filenode_uri.get_verify_cap())
450
451     def get_storage_index(self):
452         return self._filenode_uri.get_storage_index()
453
454 class DirectoryURI(_DirectoryBaseURI):
455     implements(IDirectoryURI)
456
457     BASE_STRING='URI:DIR2:'
458     BASE_STRING_RE=re.compile('^'+BASE_STRING)
459     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2'+SEP)
460     INNER_URI_CLASS=WriteableSSKFileURI
461
462     def __init__(self, filenode_uri=None):
463         if filenode_uri:
464             assert not filenode_uri.is_readonly()
465         _DirectoryBaseURI.__init__(self, filenode_uri)
466
467     def is_readonly(self):
468         return False
469
470     def get_readonly(self):
471         return ReadonlyDirectoryURI(self._filenode_uri.get_readonly())
472
473
474 class ReadonlyDirectoryURI(_DirectoryBaseURI):
475     implements(IReadonlyDirectoryURI)
476
477     BASE_STRING='URI:DIR2-RO:'
478     BASE_STRING_RE=re.compile('^'+BASE_STRING)
479     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-RO'+SEP)
480     INNER_URI_CLASS=ReadonlySSKFileURI
481
482     def __init__(self, filenode_uri=None):
483         if filenode_uri:
484             assert filenode_uri.is_readonly()
485         _DirectoryBaseURI.__init__(self, filenode_uri)
486
487     def is_readonly(self):
488         return True
489
490     def get_readonly(self):
491         return self
492
493
494 class _ImmutableDirectoryBaseURI(_DirectoryBaseURI):
495     def __init__(self, filenode_uri=None):
496         if filenode_uri:
497             assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri
498             assert not filenode_uri.is_mutable()
499         _DirectoryBaseURI.__init__(self, filenode_uri)
500
501     def is_readonly(self):
502         return True
503
504     def is_mutable(self):
505         return False
506
507     def get_readonly(self):
508         return self
509
510
511 class ImmutableDirectoryURI(_ImmutableDirectoryBaseURI):
512     BASE_STRING='URI:DIR2-CHK:'
513     BASE_STRING_RE=re.compile('^'+BASE_STRING)
514     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK'+SEP)
515     INNER_URI_CLASS=CHKFileURI
516
517     def get_verify_cap(self):
518         vcap = self._filenode_uri.get_verify_cap()
519         return ImmutableDirectoryURIVerifier(vcap)
520
521
522 class LiteralDirectoryURI(_ImmutableDirectoryBaseURI):
523     BASE_STRING='URI:DIR2-LIT:'
524     BASE_STRING_RE=re.compile('^'+BASE_STRING)
525     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-LIT'+SEP)
526     INNER_URI_CLASS=LiteralFileURI
527
528     def get_verify_cap(self):
529         # LIT caps have no verifier, since they aren't distributed
530         return None
531
532
533 def wrap_dirnode_cap(filecap):
534     if isinstance(filecap, WriteableSSKFileURI):
535         return DirectoryURI(filecap)
536     if isinstance(filecap, ReadonlySSKFileURI):
537         return ReadonlyDirectoryURI(filecap)
538     if isinstance(filecap, CHKFileURI):
539         return ImmutableDirectoryURI(filecap)
540     if isinstance(filecap, LiteralFileURI):
541         return LiteralDirectoryURI(filecap)
542     assert False, "cannot interpret as a directory cap: %s" % filecap.__class__
543
544
545 class DirectoryURIVerifier(_DirectoryBaseURI):
546     implements(IVerifierURI)
547
548     BASE_STRING='URI:DIR2-Verifier:'
549     BASE_STRING_RE=re.compile('^'+BASE_STRING)
550     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-Verifier'+SEP)
551     INNER_URI_CLASS=SSKVerifierURI
552
553     def __init__(self, filenode_uri=None):
554         if filenode_uri:
555             assert IVerifierURI.providedBy(filenode_uri)
556         self._filenode_uri = filenode_uri
557
558     def get_filenode_cap(self):
559         return self._filenode_uri
560
561     def is_mutable(self):
562         return False
563
564
565 class ImmutableDirectoryURIVerifier(DirectoryURIVerifier):
566     implements(IVerifierURI)
567     BASE_STRING='URI:DIR2-CHK-Verifier:'
568     BASE_STRING_RE=re.compile('^'+BASE_STRING)
569     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK-VERIFIER'+SEP)
570     INNER_URI_CLASS=CHKFileVerifierURI
571
572
573 class UnknownURI:
574     def __init__(self, uri, error=None):
575         self._uri = uri
576         self._error = error
577
578     def to_string(self):
579         return self._uri
580
581     def get_readonly(self):
582         return None
583
584     def get_error(self):
585         return self._error
586
587     def get_verify_cap(self):
588         return None
589
590
591 ALLEGED_READONLY_PREFIX = 'ro.'
592 ALLEGED_IMMUTABLE_PREFIX = 'imm.'
593
594 def from_string(u, deep_immutable=False, name=u"<unknown name>"):
595     if not isinstance(u, str):
596         raise TypeError("unknown URI type: %s.." % str(u)[:100])
597
598     # We allow and check ALLEGED_READONLY_PREFIX or ALLEGED_IMMUTABLE_PREFIX
599     # on all URIs, even though we would only strictly need to do so for caps of
600     # new formats (post Tahoe-LAFS 1.6). URIs that are not consistent with their
601     # prefix are treated as unknown. This should be revisited when we add the
602     # new cap formats. See <http://allmydata.org/trac/tahoe/ticket/833#comment:31>.
603     s = u
604     can_be_mutable = can_be_writeable = not deep_immutable
605     if s.startswith(ALLEGED_IMMUTABLE_PREFIX):
606         can_be_mutable = can_be_writeable = False
607         s = s[len(ALLEGED_IMMUTABLE_PREFIX):]
608     elif s.startswith(ALLEGED_READONLY_PREFIX):
609         can_be_writeable = False
610         s = s[len(ALLEGED_READONLY_PREFIX):]
611
612     error = None
613     kind = "cap"
614     try:
615         if s.startswith('URI:CHK:'):
616             return CHKFileURI.init_from_string(s)
617         elif s.startswith('URI:CHK-Verifier:'):
618             return CHKFileVerifierURI.init_from_string(s)
619         elif s.startswith('URI:LIT:'):
620             return LiteralFileURI.init_from_string(s)
621         elif s.startswith('URI:SSK:'):
622             if can_be_writeable:
623                 return WriteableSSKFileURI.init_from_string(s)
624             kind = "URI:SSK file writecap"
625         elif s.startswith('URI:SSK-RO:'):
626             if can_be_mutable:
627                 return ReadonlySSKFileURI.init_from_string(s)
628             kind = "URI:SSK-RO readcap to a mutable file"
629         elif s.startswith('URI:SSK-Verifier:'):
630             return SSKVerifierURI.init_from_string(s)
631         elif s.startswith('URI:DIR2:'):
632             if can_be_writeable:
633                 return DirectoryURI.init_from_string(s)
634             kind = "URI:DIR2 directory writecap"
635         elif s.startswith('URI:DIR2-RO:'):
636             if can_be_mutable:
637                 return ReadonlyDirectoryURI.init_from_string(s)
638             kind = "URI:DIR2-RO readcap to a mutable directory"
639         elif s.startswith('URI:DIR2-Verifier:'):
640             return DirectoryURIVerifier.init_from_string(s)
641         elif s.startswith('URI:DIR2-CHK:'):
642             return ImmutableDirectoryURI.init_from_string(s)
643         elif s.startswith('URI:DIR2-LIT:'):
644             return LiteralDirectoryURI.init_from_string(s)
645         elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable:
646             # For testing how future writeable caps would behave in read-only contexts.
647             kind = "x-tahoe-future-test-writeable: testing cap"
648         elif s.startswith('x-tahoe-future-test-mutable:') and not can_be_mutable:
649             # For testing how future mutable readcaps would behave in immutable contexts.
650             kind = "x-tahoe-future-test-mutable: testing cap"
651         else:
652             return UnknownURI(u)
653
654         # We fell through because a constraint was not met.
655         # Prefer to report the most specific constraint.
656         if not can_be_mutable:
657             error = MustBeDeepImmutableError(kind + " used in an immutable context", name)
658         else:
659             error = MustBeReadonlyError(kind + " used in a read-only context", name)
660             
661     except BadURIError, e:
662         error = e
663
664     return UnknownURI(u, error=error)
665
666 def is_uri(s):
667     try:
668         from_string(s, deep_immutable=False)
669         return True
670     except (TypeError, AssertionError):
671         return False
672
673 def is_literal_file_uri(s):
674     if not isinstance(s, str):
675         return False
676     return (s.startswith('URI:LIT:') or
677             s.startswith(ALLEGED_READONLY_PREFIX + 'URI:LIT:') or
678             s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:LIT:'))
679
680 def has_uri_prefix(s):
681     if not isinstance(s, str):
682         return False
683     return (s.startswith("URI:") or
684             s.startswith(ALLEGED_READONLY_PREFIX + 'URI:') or
685             s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:'))
686
687
688 # These take the same keyword arguments as from_string above.
689
690 def from_string_dirnode(s, **kwargs):
691     u = from_string(s, **kwargs)
692     assert IDirnodeURI.providedBy(u)
693     return u
694
695 registerAdapter(from_string_dirnode, str, IDirnodeURI)
696
697 def from_string_filenode(s, **kwargs):
698     u = from_string(s, **kwargs)
699     assert IFileURI.providedBy(u)
700     return u
701
702 registerAdapter(from_string_filenode, str, IFileURI)
703
704 def from_string_mutable_filenode(s, **kwargs):
705     u = from_string(s, **kwargs)
706     assert IMutableFileURI.providedBy(u)
707     return u
708 registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
709
710 def from_string_verifier(s, **kwargs):
711     u = from_string(s, **kwargs)
712     assert IVerifierURI.providedBy(u)
713     return u
714 registerAdapter(from_string_verifier, str, IVerifierURI)
715
716
717 def pack_extension(data):
718     pieces = []
719     for k in sorted(data.keys()):
720         value = data[k]
721         if isinstance(value, (int, long)):
722             value = "%d" % value
723         assert isinstance(value, str), k
724         assert re.match(r'^[a-zA-Z_\-]+$', k)
725         pieces.append(k + ':' + hashutil.netstring(value))
726     uri_extension = ''.join(pieces)
727     return uri_extension
728
729 def unpack_extension(data):
730     d = {}
731     while data:
732         colon = data.index(':')
733         key = data[:colon]
734         data = data[colon+1:]
735
736         colon = data.index(':')
737         number = data[:colon]
738         length = int(number)
739         data = data[colon+1:]
740
741         value = data[:length]
742         assert data[length] == ','
743         data = data[length+1:]
744
745         d[key] = value
746
747     # convert certain things to numbers
748     for intkey in ('size', 'segment_size', 'num_segments',
749                    'needed_shares', 'total_shares'):
750         if intkey in d:
751             d[intkey] = int(d[intkey])
752     return d
753
754
755 def unpack_extension_readable(data):
756     unpacked = unpack_extension(data)
757     unpacked["UEB_hash"] = hashutil.uri_extension_hash(data)
758     for k in sorted(unpacked.keys()):
759         if 'hash' in k:
760             unpacked[k] = base32.b2a(unpacked[k])
761     return unpacked
762