]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/uri.py
372e0b8be2605eb49c755b7a6aa45b588be1901f
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / uri.py
1
2 import re
3
4 from zope.interface import implements
5 from twisted.python.components import registerAdapter
6
7 from allmydata.storage.server import si_a2b, si_b2a
8 from allmydata.util import base32, hashutil
9 from allmydata.util.assertutil import _assert
10 from allmydata.interfaces import IURI, IDirnodeURI, IFileURI, IImmutableFileURI, \
11     IVerifierURI, IMutableFileURI, IDirectoryURI, IReadonlyDirectoryURI, \
12     MustBeDeepImmutableError, MustBeReadonlyError, CapConstraintError
13
14 class BadURIError(CapConstraintError):
15     pass
16
17 # The URI shall be an ASCII representation of a reference to the file/directory.
18 # It shall contain enough information to retrieve and validate the contents.
19 # It shall be expressed in a limited character set (currently base32 plus ':' and
20 # capital letters, but future URIs might use a larger charset).
21
22 # TODO:
23 #  - rename all of the *URI classes/interfaces to *Cap
24 #  - make variable and method names consistently use _uri for an URI string,
25 #    and _cap for a Cap object (decoded URI)
26
27 BASE32STR_128bits = '(%s{25}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_3bits)
28 BASE32STR_256bits = '(%s{51}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_1bits)
29
30 NUMBER='([0-9]+)'
31
32
33 class _BaseURI:
34     def __hash__(self):
35         return self.to_string().__hash__()
36
37     def __eq__(self, them):
38         if isinstance(them, _BaseURI):
39             return self.to_string() == them.to_string()
40         else:
41             return False
42
43     def __ne__(self, them):
44         if isinstance(them, _BaseURI):
45             return self.to_string() != them.to_string()
46         else:
47             return True
48
49     def get_storage_index(self):
50         return self.storage_index
51
52
53 class CHKFileURI(_BaseURI):
54     implements(IURI, IImmutableFileURI)
55
56     BASE_STRING='URI:CHK:'
57     STRING_RE=re.compile('^URI:CHK:'+BASE32STR_128bits+':'+
58                          BASE32STR_256bits+':'+NUMBER+':'+NUMBER+':'+NUMBER+
59                          '$')
60
61     def __init__(self, key, uri_extension_hash, needed_shares, total_shares,
62                  size):
63         self.key = key
64         self.uri_extension_hash = uri_extension_hash
65         self.needed_shares = needed_shares
66         self.total_shares = total_shares
67         self.size = size
68         self.storage_index = hashutil.storage_index_hash(self.key)
69         if not len(self.storage_index) == 16: # sha256 hash truncated to 128
70             raise BadURIError("storage index must be 16 bytes long")
71
72     @classmethod
73     def init_from_string(cls, uri):
74         mo = cls.STRING_RE.search(uri)
75         if not mo:
76             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
77         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
78                    int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
79
80     def to_string(self):
81         assert isinstance(self.needed_shares, int)
82         assert isinstance(self.total_shares, int)
83         assert isinstance(self.size, (int,long))
84
85         return ('URI:CHK:%s:%s:%d:%d:%d' %
86                 (base32.b2a(self.key),
87                  base32.b2a(self.uri_extension_hash),
88                  self.needed_shares,
89                  self.total_shares,
90                  self.size))
91
92     def is_readonly(self):
93         return True
94
95     def is_mutable(self):
96         return False
97
98     def get_readonly(self):
99         return self
100
101     def get_size(self):
102         return self.size
103
104     def get_verify_cap(self):
105         return CHKFileVerifierURI(storage_index=self.storage_index,
106                                   uri_extension_hash=self.uri_extension_hash,
107                                   needed_shares=self.needed_shares,
108                                   total_shares=self.total_shares,
109                                   size=self.size)
110
111
112 class CHKFileVerifierURI(_BaseURI):
113     implements(IVerifierURI)
114
115     BASE_STRING='URI:CHK-Verifier:'
116     STRING_RE=re.compile('^URI:CHK-Verifier:'+BASE32STR_128bits+':'+
117                          BASE32STR_256bits+':'+NUMBER+':'+NUMBER+':'+NUMBER)
118
119     def __init__(self, storage_index, uri_extension_hash,
120                  needed_shares, total_shares, size):
121         assert len(storage_index) == 16
122         self.storage_index = storage_index
123         self.uri_extension_hash = uri_extension_hash
124         self.needed_shares = needed_shares
125         self.total_shares = total_shares
126         self.size = size
127
128     @classmethod
129     def init_from_string(cls, uri):
130         mo = cls.STRING_RE.search(uri)
131         if not mo:
132             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
133         return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)),
134                    int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
135
136     def to_string(self):
137         assert isinstance(self.needed_shares, int)
138         assert isinstance(self.total_shares, int)
139         assert isinstance(self.size, (int,long))
140
141         return ('URI:CHK-Verifier:%s:%s:%d:%d:%d' %
142                 (si_b2a(self.storage_index),
143                  base32.b2a(self.uri_extension_hash),
144                  self.needed_shares,
145                  self.total_shares,
146                  self.size))
147
148     def is_readonly(self):
149         return True
150
151     def is_mutable(self):
152         return False
153
154     def get_readonly(self):
155         return self
156
157     def get_verify_cap(self):
158         return self
159
160
161 class LiteralFileURI(_BaseURI):
162     implements(IURI, IImmutableFileURI)
163
164     BASE_STRING='URI:LIT:'
165     STRING_RE=re.compile('^URI:LIT:'+base32.BASE32STR_anybytes+'$')
166
167     def __init__(self, data=None):
168         if data is not None:
169             assert isinstance(data, str)
170             self.data = data
171
172     @classmethod
173     def init_from_string(cls, uri):
174         mo = cls.STRING_RE.search(uri)
175         if not mo:
176             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
177         return cls(base32.a2b(mo.group(1)))
178
179     def to_string(self):
180         return 'URI:LIT:%s' % base32.b2a(self.data)
181
182     def is_readonly(self):
183         return True
184
185     def is_mutable(self):
186         return False
187
188     def get_readonly(self):
189         return self
190
191     def get_storage_index(self):
192         return None
193
194     def get_verify_cap(self):
195         # LIT files need no verification, all the data is present in the URI
196         return None
197
198     def get_size(self):
199         return len(self.data)
200
201
202 class WriteableSSKFileURI(_BaseURI):
203     implements(IURI, IMutableFileURI)
204
205     BASE_STRING='URI:SSK:'
206     STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+
207                          BASE32STR_256bits+'$')
208
209     def __init__(self, writekey, fingerprint):
210         self.writekey = writekey
211         self.readkey = hashutil.ssk_readkey_hash(writekey)
212         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
213         assert len(self.storage_index) == 16
214         self.fingerprint = fingerprint
215
216     @classmethod
217     def init_from_string(cls, uri):
218         mo = cls.STRING_RE.search(uri)
219         if not mo:
220             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
221         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
222
223     def to_string(self):
224         assert isinstance(self.writekey, str)
225         assert isinstance(self.fingerprint, str)
226         return 'URI:SSK:%s:%s' % (base32.b2a(self.writekey),
227                                   base32.b2a(self.fingerprint))
228
229     def __repr__(self):
230         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
231
232     def abbrev(self):
233         return base32.b2a(self.writekey[:5])
234
235     def abbrev_si(self):
236         return base32.b2a(self.storage_index)[:5]
237
238     def is_readonly(self):
239         return False
240
241     def is_mutable(self):
242         return True
243
244     def get_readonly(self):
245         return ReadonlySSKFileURI(self.readkey, self.fingerprint)
246
247     def get_verify_cap(self):
248         return SSKVerifierURI(self.storage_index, self.fingerprint)
249
250
251 class ReadonlySSKFileURI(_BaseURI):
252     implements(IURI, IMutableFileURI)
253
254     BASE_STRING='URI:SSK-RO:'
255     STRING_RE=re.compile('^URI:SSK-RO:'+BASE32STR_128bits+':'+BASE32STR_256bits+'$')
256
257     def __init__(self, readkey, fingerprint):
258         self.readkey = readkey
259         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
260         assert len(self.storage_index) == 16
261         self.fingerprint = fingerprint
262
263     @classmethod
264     def init_from_string(cls, uri):
265         mo = cls.STRING_RE.search(uri)
266         if not mo:
267             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
268         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
269
270     def to_string(self):
271         assert isinstance(self.readkey, str)
272         assert isinstance(self.fingerprint, str)
273         return 'URI:SSK-RO:%s:%s' % (base32.b2a(self.readkey),
274                                      base32.b2a(self.fingerprint))
275
276     def __repr__(self):
277         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
278
279     def abbrev(self):
280         return base32.b2a(self.readkey[:5])
281
282     def abbrev_si(self):
283         return base32.b2a(self.storage_index)[:5]
284
285     def is_readonly(self):
286         return True
287
288     def is_mutable(self):
289         return True
290
291     def get_readonly(self):
292         return self
293
294     def get_verify_cap(self):
295         return SSKVerifierURI(self.storage_index, self.fingerprint)
296
297
298 class SSKVerifierURI(_BaseURI):
299     implements(IVerifierURI)
300
301     BASE_STRING='URI:SSK-Verifier:'
302     STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'$')
303
304     def __init__(self, storage_index, fingerprint):
305         assert len(storage_index) == 16
306         self.storage_index = storage_index
307         self.fingerprint = fingerprint
308
309     @classmethod
310     def init_from_string(cls, uri):
311         mo = cls.STRING_RE.search(uri)
312         if not mo:
313             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
314         return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
315
316     def to_string(self):
317         assert isinstance(self.storage_index, str)
318         assert isinstance(self.fingerprint, str)
319         return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index),
320                                            base32.b2a(self.fingerprint))
321
322     def is_readonly(self):
323         return True
324
325     def is_mutable(self):
326         return False
327
328     def get_readonly(self):
329         return self
330
331     def get_verify_cap(self):
332         return self
333
334
335 class WriteableMDMFFileURI(_BaseURI):
336     implements(IURI, IMutableFileURI)
337
338     BASE_STRING='URI:MDMF:'
339     STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'(:|$)')
340
341     def __init__(self, writekey, fingerprint):
342         self.writekey = writekey
343         self.readkey = hashutil.ssk_readkey_hash(writekey)
344         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
345         assert len(self.storage_index) == 16
346         self.fingerprint = fingerprint
347
348     @classmethod
349     def init_from_string(cls, uri):
350         mo = cls.STRING_RE.search(uri)
351         if not mo:
352             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
353         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
354
355     def to_string(self):
356         assert isinstance(self.writekey, str)
357         assert isinstance(self.fingerprint, str)
358         ret = 'URI:MDMF:%s:%s' % (base32.b2a(self.writekey),
359                                   base32.b2a(self.fingerprint))
360         return ret
361
362     def __repr__(self):
363         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
364
365     def abbrev(self):
366         return base32.b2a(self.writekey[:5])
367
368     def abbrev_si(self):
369         return base32.b2a(self.storage_index)[:5]
370
371     def is_readonly(self):
372         return False
373
374     def is_mutable(self):
375         return True
376
377     def get_readonly(self):
378         return ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
379
380     def get_verify_cap(self):
381         return MDMFVerifierURI(self.storage_index, self.fingerprint)
382
383
384 class ReadonlyMDMFFileURI(_BaseURI):
385     implements(IURI, IMutableFileURI)
386
387     BASE_STRING='URI:MDMF-RO:'
388     STRING_RE=re.compile('^' +BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'(:|$)')
389
390     def __init__(self, readkey, fingerprint):
391         self.readkey = readkey
392         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
393         assert len(self.storage_index) == 16
394         self.fingerprint = fingerprint
395
396     @classmethod
397     def init_from_string(cls, uri):
398         mo = cls.STRING_RE.search(uri)
399         if not mo:
400             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
401
402         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
403
404     def to_string(self):
405         assert isinstance(self.readkey, str)
406         assert isinstance(self.fingerprint, str)
407         ret = 'URI:MDMF-RO:%s:%s' % (base32.b2a(self.readkey),
408                                      base32.b2a(self.fingerprint))
409         return ret
410
411     def __repr__(self):
412         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
413
414     def abbrev(self):
415         return base32.b2a(self.readkey[:5])
416
417     def abbrev_si(self):
418         return base32.b2a(self.storage_index)[:5]
419
420     def is_readonly(self):
421         return True
422
423     def is_mutable(self):
424         return True
425
426     def get_readonly(self):
427         return self
428
429     def get_verify_cap(self):
430         return MDMFVerifierURI(self.storage_index, self.fingerprint)
431
432
433 class MDMFVerifierURI(_BaseURI):
434     implements(IVerifierURI)
435
436     BASE_STRING='URI:MDMF-Verifier:'
437     STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'(:|$)')
438
439     def __init__(self, storage_index, fingerprint):
440         assert len(storage_index) == 16
441         self.storage_index = storage_index
442         self.fingerprint = fingerprint
443
444     @classmethod
445     def init_from_string(cls, uri):
446         mo = cls.STRING_RE.search(uri)
447         if not mo:
448             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
449         return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
450
451     def to_string(self):
452         assert isinstance(self.storage_index, str)
453         assert isinstance(self.fingerprint, str)
454         ret = 'URI:MDMF-Verifier:%s:%s' % (si_b2a(self.storage_index),
455                                            base32.b2a(self.fingerprint))
456         return ret
457
458     def is_readonly(self):
459         return True
460
461     def is_mutable(self):
462         return False
463
464     def get_readonly(self):
465         return self
466
467     def get_verify_cap(self):
468         return self
469
470
471 class _DirectoryBaseURI(_BaseURI):
472     implements(IURI, IDirnodeURI)
473     def __init__(self, filenode_uri=None):
474         self._filenode_uri = filenode_uri
475
476     def __repr__(self):
477         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
478
479     @classmethod
480     def init_from_string(cls, uri):
481         mo = cls.BASE_STRING_RE.search(uri)
482         if not mo:
483             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
484         bits = uri[mo.end():]
485         fn = cls.INNER_URI_CLASS.init_from_string(
486             cls.INNER_URI_CLASS.BASE_STRING+bits)
487         return cls(fn)
488
489     def to_string(self):
490         fnuri = self._filenode_uri.to_string()
491         mo = re.match(self.INNER_URI_CLASS.BASE_STRING, fnuri)
492         assert mo, fnuri
493         bits = fnuri[mo.end():]
494         return self.BASE_STRING+bits
495
496     def abbrev(self):
497         return self._filenode_uri.to_string().split(':')[2][:5]
498
499     def abbrev_si(self):
500         si = self._filenode_uri.get_storage_index()
501         if si is None:
502             return "<LIT>"
503         return base32.b2a(si)[:5]
504
505     def is_mutable(self):
506         return True
507
508     def get_filenode_cap(self):
509         return self._filenode_uri
510
511     def get_verify_cap(self):
512         return DirectoryURIVerifier(self._filenode_uri.get_verify_cap())
513
514     def get_storage_index(self):
515         return self._filenode_uri.get_storage_index()
516
517
518 class DirectoryURI(_DirectoryBaseURI):
519     implements(IDirectoryURI)
520
521     BASE_STRING='URI:DIR2:'
522     BASE_STRING_RE=re.compile('^'+BASE_STRING)
523     INNER_URI_CLASS=WriteableSSKFileURI
524
525     def __init__(self, filenode_uri=None):
526         if filenode_uri:
527             assert not filenode_uri.is_readonly()
528         _DirectoryBaseURI.__init__(self, filenode_uri)
529
530     def is_readonly(self):
531         return False
532
533     def get_readonly(self):
534         return ReadonlyDirectoryURI(self._filenode_uri.get_readonly())
535
536
537 class ReadonlyDirectoryURI(_DirectoryBaseURI):
538     implements(IReadonlyDirectoryURI)
539
540     BASE_STRING='URI:DIR2-RO:'
541     BASE_STRING_RE=re.compile('^'+BASE_STRING)
542     INNER_URI_CLASS=ReadonlySSKFileURI
543
544     def __init__(self, filenode_uri=None):
545         if filenode_uri:
546             assert filenode_uri.is_readonly()
547         _DirectoryBaseURI.__init__(self, filenode_uri)
548
549     def is_readonly(self):
550         return True
551
552     def get_readonly(self):
553         return self
554
555
556 class _ImmutableDirectoryBaseURI(_DirectoryBaseURI):
557     def __init__(self, filenode_uri=None):
558         if filenode_uri:
559             assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri
560             assert not filenode_uri.is_mutable()
561         _DirectoryBaseURI.__init__(self, filenode_uri)
562
563     def is_readonly(self):
564         return True
565
566     def is_mutable(self):
567         return False
568
569     def get_readonly(self):
570         return self
571
572
573 class ImmutableDirectoryURI(_ImmutableDirectoryBaseURI):
574     BASE_STRING='URI:DIR2-CHK:'
575     BASE_STRING_RE=re.compile('^'+BASE_STRING)
576     INNER_URI_CLASS=CHKFileURI
577
578     def get_verify_cap(self):
579         vcap = self._filenode_uri.get_verify_cap()
580         return ImmutableDirectoryURIVerifier(vcap)
581
582
583 class LiteralDirectoryURI(_ImmutableDirectoryBaseURI):
584     BASE_STRING='URI:DIR2-LIT:'
585     BASE_STRING_RE=re.compile('^'+BASE_STRING)
586     INNER_URI_CLASS=LiteralFileURI
587
588     def get_verify_cap(self):
589         # LIT caps have no verifier, since they aren't distributed
590         return None
591
592
593 class MDMFDirectoryURI(_DirectoryBaseURI):
594     implements(IDirectoryURI)
595
596     BASE_STRING='URI:DIR2-MDMF:'
597     BASE_STRING_RE=re.compile('^'+BASE_STRING)
598     INNER_URI_CLASS=WriteableMDMFFileURI
599
600     def __init__(self, filenode_uri=None):
601         if filenode_uri:
602             assert not filenode_uri.is_readonly()
603         _DirectoryBaseURI.__init__(self, filenode_uri)
604
605     def is_readonly(self):
606         return False
607
608     def get_readonly(self):
609         return ReadonlyMDMFDirectoryURI(self._filenode_uri.get_readonly())
610
611     def get_verify_cap(self):
612         return MDMFDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
613
614
615 class ReadonlyMDMFDirectoryURI(_DirectoryBaseURI):
616     implements(IReadonlyDirectoryURI)
617
618     BASE_STRING='URI:DIR2-MDMF-RO:'
619     BASE_STRING_RE=re.compile('^'+BASE_STRING)
620     INNER_URI_CLASS=ReadonlyMDMFFileURI
621
622     def __init__(self, filenode_uri=None):
623         if filenode_uri:
624             assert filenode_uri.is_readonly()
625         _DirectoryBaseURI.__init__(self, filenode_uri)
626
627     def is_readonly(self):
628         return True
629
630     def get_readonly(self):
631         return self
632
633     def get_verify_cap(self):
634         return MDMFDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
635
636
637 def wrap_dirnode_cap(filecap):
638     if isinstance(filecap, WriteableSSKFileURI):
639         return DirectoryURI(filecap)
640     if isinstance(filecap, ReadonlySSKFileURI):
641         return ReadonlyDirectoryURI(filecap)
642     if isinstance(filecap, CHKFileURI):
643         return ImmutableDirectoryURI(filecap)
644     if isinstance(filecap, LiteralFileURI):
645         return LiteralDirectoryURI(filecap)
646     if isinstance(filecap, WriteableMDMFFileURI):
647         return MDMFDirectoryURI(filecap)
648     if isinstance(filecap, ReadonlyMDMFFileURI):
649         return ReadonlyMDMFDirectoryURI(filecap)
650     raise AssertionError("cannot interpret as a directory cap: %s" % filecap.__class__)
651
652
653 class MDMFDirectoryURIVerifier(_DirectoryBaseURI):
654     implements(IVerifierURI)
655
656     BASE_STRING='URI:DIR2-MDMF-Verifier:'
657     BASE_STRING_RE=re.compile('^'+BASE_STRING)
658     INNER_URI_CLASS=MDMFVerifierURI
659
660     def __init__(self, filenode_uri=None):
661         if filenode_uri:
662             _assert(IVerifierURI.providedBy(filenode_uri))
663         self._filenode_uri = filenode_uri
664
665     def get_filenode_cap(self):
666         return self._filenode_uri
667
668     def is_mutable(self):
669         return False
670
671     def is_readonly(self):
672         return True
673
674     def get_readonly(self):
675         return self
676
677
678 class DirectoryURIVerifier(_DirectoryBaseURI):
679     implements(IVerifierURI)
680
681     BASE_STRING='URI:DIR2-Verifier:'
682     BASE_STRING_RE=re.compile('^'+BASE_STRING)
683     INNER_URI_CLASS=SSKVerifierURI
684
685     def __init__(self, filenode_uri=None):
686         if filenode_uri:
687             _assert(IVerifierURI.providedBy(filenode_uri))
688         self._filenode_uri = filenode_uri
689
690     def get_filenode_cap(self):
691         return self._filenode_uri
692
693     def is_mutable(self):
694         return False
695
696     def is_readonly(self):
697         return True
698
699     def get_readonly(self):
700         return self
701
702
703 class ImmutableDirectoryURIVerifier(DirectoryURIVerifier):
704     implements(IVerifierURI)
705     BASE_STRING='URI:DIR2-CHK-Verifier:'
706     BASE_STRING_RE=re.compile('^'+BASE_STRING)
707     INNER_URI_CLASS=CHKFileVerifierURI
708
709
710 class UnknownURI:
711     def __init__(self, uri, error=None):
712         self._uri = uri
713         self._error = error
714
715     def to_string(self):
716         return self._uri
717
718     def get_readonly(self):
719         return None
720
721     def get_error(self):
722         return self._error
723
724     def get_verify_cap(self):
725         return None
726
727
728 ALLEGED_READONLY_PREFIX = 'ro.'
729 ALLEGED_IMMUTABLE_PREFIX = 'imm.'
730
731 def from_string(u, deep_immutable=False, name=u"<unknown name>"):
732     if not isinstance(u, str):
733         raise TypeError("unknown URI type: %s.." % str(u)[:100])
734
735     # We allow and check ALLEGED_READONLY_PREFIX or ALLEGED_IMMUTABLE_PREFIX
736     # on all URIs, even though we would only strictly need to do so for caps of
737     # new formats (post Tahoe-LAFS 1.6). URIs that are not consistent with their
738     # prefix are treated as unknown. This should be revisited when we add the
739     # new cap formats. See ticket #833 comment:31.
740     s = u
741     can_be_mutable = can_be_writeable = not deep_immutable
742     if s.startswith(ALLEGED_IMMUTABLE_PREFIX):
743         can_be_mutable = can_be_writeable = False
744         s = s[len(ALLEGED_IMMUTABLE_PREFIX):]
745     elif s.startswith(ALLEGED_READONLY_PREFIX):
746         can_be_writeable = False
747         s = s[len(ALLEGED_READONLY_PREFIX):]
748
749     error = None
750     kind = "cap"
751     try:
752         if s.startswith('URI:CHK:'):
753             return CHKFileURI.init_from_string(s)
754         elif s.startswith('URI:CHK-Verifier:'):
755             return CHKFileVerifierURI.init_from_string(s)
756         elif s.startswith('URI:LIT:'):
757             return LiteralFileURI.init_from_string(s)
758         elif s.startswith('URI:SSK:'):
759             if can_be_writeable:
760                 return WriteableSSKFileURI.init_from_string(s)
761             kind = "URI:SSK file writecap"
762         elif s.startswith('URI:SSK-RO:'):
763             if can_be_mutable:
764                 return ReadonlySSKFileURI.init_from_string(s)
765             kind = "URI:SSK-RO readcap to a mutable file"
766         elif s.startswith('URI:SSK-Verifier:'):
767             return SSKVerifierURI.init_from_string(s)
768         elif s.startswith('URI:MDMF:'):
769             if can_be_writeable:
770                 return WriteableMDMFFileURI.init_from_string(s)
771             kind = "URI:MDMF file writecap"
772         elif s.startswith('URI:MDMF-RO:'):
773             if can_be_mutable:
774                 return ReadonlyMDMFFileURI.init_from_string(s)
775             kind = "URI:MDMF-RO readcap to a mutable file"
776         elif s.startswith('URI:MDMF-Verifier:'):
777             return MDMFVerifierURI.init_from_string(s)
778         elif s.startswith('URI:DIR2:'):
779             if can_be_writeable:
780                 return DirectoryURI.init_from_string(s)
781             kind = "URI:DIR2 directory writecap"
782         elif s.startswith('URI:DIR2-RO:'):
783             if can_be_mutable:
784                 return ReadonlyDirectoryURI.init_from_string(s)
785             kind = "URI:DIR2-RO readcap to a mutable directory"
786         elif s.startswith('URI:DIR2-Verifier:'):
787             return DirectoryURIVerifier.init_from_string(s)
788         elif s.startswith('URI:DIR2-CHK:'):
789             return ImmutableDirectoryURI.init_from_string(s)
790         elif s.startswith('URI:DIR2-CHK-Verifier:'):
791             return ImmutableDirectoryURIVerifier.init_from_string(s)
792         elif s.startswith('URI:DIR2-LIT:'):
793             return LiteralDirectoryURI.init_from_string(s)
794         elif s.startswith('URI:DIR2-MDMF:'):
795             if can_be_writeable:
796                 return MDMFDirectoryURI.init_from_string(s)
797             kind = "URI:DIR2-MDMF directory writecap"
798         elif s.startswith('URI:DIR2-MDMF-RO:'):
799             if can_be_mutable:
800                 return ReadonlyMDMFDirectoryURI.init_from_string(s)
801             kind = "URI:DIR2-MDMF-RO readcap to a mutable directory"
802         elif s.startswith('URI:DIR2-MDMF-Verifier:'):
803             return MDMFDirectoryURIVerifier.init_from_string(s)
804         elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable:
805             # For testing how future writeable caps would behave in read-only contexts.
806             kind = "x-tahoe-future-test-writeable: testing cap"
807         elif s.startswith('x-tahoe-future-test-mutable:') and not can_be_mutable:
808             # For testing how future mutable readcaps would behave in immutable contexts.
809             kind = "x-tahoe-future-test-mutable: testing cap"
810         else:
811             return UnknownURI(u)
812
813         # We fell through because a constraint was not met.
814         # Prefer to report the most specific constraint.
815         if not can_be_mutable:
816             error = MustBeDeepImmutableError(kind + " used in an immutable context", name)
817         else:
818             error = MustBeReadonlyError(kind + " used in a read-only context", name)
819
820     except BadURIError, e:
821         error = e
822
823     return UnknownURI(u, error=error)
824
825 def is_uri(s):
826     try:
827         from_string(s, deep_immutable=False)
828         return True
829     except (TypeError, AssertionError):
830         return False
831
832 def is_literal_file_uri(s):
833     if not isinstance(s, str):
834         return False
835     return (s.startswith('URI:LIT:') or
836             s.startswith(ALLEGED_READONLY_PREFIX + 'URI:LIT:') or
837             s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:LIT:'))
838
839 def has_uri_prefix(s):
840     if not isinstance(s, str):
841         return False
842     return (s.startswith("URI:") or
843             s.startswith(ALLEGED_READONLY_PREFIX + 'URI:') or
844             s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:'))
845
846
847 # These take the same keyword arguments as from_string above.
848
849 def from_string_dirnode(s, **kwargs):
850     u = from_string(s, **kwargs)
851     _assert(IDirnodeURI.providedBy(u))
852     return u
853
854 registerAdapter(from_string_dirnode, str, IDirnodeURI)
855
856 def from_string_filenode(s, **kwargs):
857     u = from_string(s, **kwargs)
858     _assert(IFileURI.providedBy(u))
859     return u
860
861 registerAdapter(from_string_filenode, str, IFileURI)
862
863 def from_string_mutable_filenode(s, **kwargs):
864     u = from_string(s, **kwargs)
865     _assert(IMutableFileURI.providedBy(u))
866     return u
867 registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
868
869 def from_string_verifier(s, **kwargs):
870     u = from_string(s, **kwargs)
871     _assert(IVerifierURI.providedBy(u))
872     return u
873 registerAdapter(from_string_verifier, str, IVerifierURI)
874
875
876 def pack_extension(data):
877     pieces = []
878     for k in sorted(data.keys()):
879         value = data[k]
880         if isinstance(value, (int, long)):
881             value = "%d" % value
882         assert isinstance(value, str), k
883         assert re.match(r'^[a-zA-Z_\-]+$', k)
884         pieces.append(k + ':' + hashutil.netstring(value))
885     uri_extension = ''.join(pieces)
886     return uri_extension
887
888 def unpack_extension(data):
889     d = {}
890     while data:
891         colon = data.index(':')
892         key = data[:colon]
893         data = data[colon+1:]
894
895         colon = data.index(':')
896         number = data[:colon]
897         length = int(number)
898         data = data[colon+1:]
899
900         value = data[:length]
901         assert data[length] == ','
902         data = data[length+1:]
903
904         d[key] = value
905
906     # convert certain things to numbers
907     for intkey in ('size', 'segment_size', 'num_segments',
908                    'needed_shares', 'total_shares'):
909         if intkey in d:
910             d[intkey] = int(d[intkey])
911     return d
912
913
914 def unpack_extension_readable(data):
915     unpacked = unpack_extension(data)
916     unpacked["UEB_hash"] = hashutil.uri_extension_hash(data)
917     for k in sorted(unpacked.keys()):
918         if 'hash' in k:
919             unpacked[k] = base32.b2a(unpacked[k])
920     return unpacked
921