source: trunk/src/allmydata/uri.py

Last change on this file was 0e5b6da, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-27T23:53:49Z

remove more Python2: unicode -> str, long -> int

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