]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/uri.py
ee9c86aaa52ff0f4b2d636fbb252d7e7600f6d66
[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 class ReadonlySSKFileURI(_BaseURI):
297     implements(IURI, IMutableFileURI)
298
299     BASE_STRING='URI:SSK-RO:'
300     STRING_RE=re.compile('^URI:SSK-RO:'+BASE32STR_128bits+':'+BASE32STR_256bits+'$')
301     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK-RO'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
302
303     def __init__(self, readkey, fingerprint):
304         self.readkey = readkey
305         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
306         assert len(self.storage_index) == 16
307         self.fingerprint = fingerprint
308
309     @classmethod
310     def init_from_human_encoding(cls, uri):
311         mo = cls.HUMAN_RE.search(uri)
312         if not mo:
313             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
314         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
315
316     @classmethod
317     def init_from_string(cls, uri):
318         mo = cls.STRING_RE.search(uri)
319         if not mo:
320             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
321         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
322
323     def to_string(self):
324         assert isinstance(self.readkey, str)
325         assert isinstance(self.fingerprint, str)
326         return 'URI:SSK-RO:%s:%s' % (base32.b2a(self.readkey),
327                                      base32.b2a(self.fingerprint))
328
329     def __repr__(self):
330         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
331
332     def abbrev(self):
333         return base32.b2a(self.readkey[:5])
334
335     def abbrev_si(self):
336         return base32.b2a(self.storage_index)[:5]
337
338     def is_readonly(self):
339         return True
340
341     def is_mutable(self):
342         return True
343
344     def get_readonly(self):
345         return self
346
347     def get_verify_cap(self):
348         return SSKVerifierURI(self.storage_index, self.fingerprint)
349
350 class SSKVerifierURI(_BaseURI):
351     implements(IVerifierURI)
352
353     BASE_STRING='URI:SSK-Verifier:'
354     STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'$')
355     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK-Verifier'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
356
357     def __init__(self, storage_index, fingerprint):
358         assert len(storage_index) == 16
359         self.storage_index = storage_index
360         self.fingerprint = fingerprint
361
362     @classmethod
363     def init_from_human_encoding(cls, uri):
364         mo = cls.HUMAN_RE.search(uri)
365         if not mo:
366             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
367         return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
368
369     @classmethod
370     def init_from_string(cls, uri):
371         mo = cls.STRING_RE.search(uri)
372         if not mo:
373             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
374         return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
375
376     def to_string(self):
377         assert isinstance(self.storage_index, str)
378         assert isinstance(self.fingerprint, str)
379         return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index),
380                                            base32.b2a(self.fingerprint))
381
382     def is_readonly(self):
383         return True
384
385     def is_mutable(self):
386         return False
387
388     def get_readonly(self):
389         return self
390
391     def get_verify_cap(self):
392         return self
393
394 class WriteableMDMFFileURI(_BaseURI):
395     implements(IURI, IMutableFileURI)
396
397     BASE_STRING='URI:MDMF:'
398     STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'(:|$)')
399     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'(:|$)')
400
401     def __init__(self, writekey, fingerprint):
402         self.writekey = writekey
403         self.readkey = hashutil.ssk_readkey_hash(writekey)
404         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
405         assert len(self.storage_index) == 16
406         self.fingerprint = fingerprint
407
408     @classmethod
409     def init_from_human_encoding(cls, uri):
410         mo = cls.HUMAN_RE.search(uri)
411         if not mo:
412             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
413         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
414
415     @classmethod
416     def init_from_string(cls, uri):
417         mo = cls.STRING_RE.search(uri)
418         if not mo:
419             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
420         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
421
422     def to_string(self):
423         assert isinstance(self.writekey, str)
424         assert isinstance(self.fingerprint, str)
425         ret = 'URI:MDMF:%s:%s' % (base32.b2a(self.writekey),
426                                   base32.b2a(self.fingerprint))
427         return ret
428
429     def __repr__(self):
430         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
431
432     def abbrev(self):
433         return base32.b2a(self.writekey[:5])
434
435     def abbrev_si(self):
436         return base32.b2a(self.storage_index)[:5]
437
438     def is_readonly(self):
439         return False
440
441     def is_mutable(self):
442         return True
443
444     def get_readonly(self):
445         return ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
446
447     def get_verify_cap(self):
448         return MDMFVerifierURI(self.storage_index, self.fingerprint)
449
450 class ReadonlyMDMFFileURI(_BaseURI):
451     implements(IURI, IMutableFileURI)
452
453     BASE_STRING='URI:MDMF-RO:'
454     STRING_RE=re.compile('^' +BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'(:|$)')
455     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF-RO'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'(:|$)')
456
457     def __init__(self, readkey, fingerprint):
458         self.readkey = readkey
459         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
460         assert len(self.storage_index) == 16
461         self.fingerprint = fingerprint
462
463     @classmethod
464     def init_from_human_encoding(cls, uri):
465         mo = cls.HUMAN_RE.search(uri)
466         if not mo:
467             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
468         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
469
470     @classmethod
471     def init_from_string(cls, uri):
472         mo = cls.STRING_RE.search(uri)
473         if not mo:
474             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
475
476         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
477
478     def to_string(self):
479         assert isinstance(self.readkey, str)
480         assert isinstance(self.fingerprint, str)
481         ret = 'URI:MDMF-RO:%s:%s' % (base32.b2a(self.readkey),
482                                      base32.b2a(self.fingerprint))
483         return ret
484
485     def __repr__(self):
486         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
487
488     def abbrev(self):
489         return base32.b2a(self.readkey[:5])
490
491     def abbrev_si(self):
492         return base32.b2a(self.storage_index)[:5]
493
494     def is_readonly(self):
495         return True
496
497     def is_mutable(self):
498         return True
499
500     def get_readonly(self):
501         return self
502
503     def get_verify_cap(self):
504         return MDMFVerifierURI(self.storage_index, self.fingerprint)
505
506 class MDMFVerifierURI(_BaseURI):
507     implements(IVerifierURI)
508
509     BASE_STRING='URI:MDMF-Verifier:'
510     STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'(:|$)')
511     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF-Verifier'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'(:|$)')
512
513     def __init__(self, storage_index, fingerprint):
514         assert len(storage_index) == 16
515         self.storage_index = storage_index
516         self.fingerprint = fingerprint
517
518     @classmethod
519     def init_from_human_encoding(cls, uri):
520         mo = cls.HUMAN_RE.search(uri)
521         if not mo:
522             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
523         return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
524
525     @classmethod
526     def init_from_string(cls, uri):
527         mo = cls.STRING_RE.search(uri)
528         if not mo:
529             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
530         return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
531
532     def to_string(self):
533         assert isinstance(self.storage_index, str)
534         assert isinstance(self.fingerprint, str)
535         ret = 'URI:MDMF-Verifier:%s:%s' % (si_b2a(self.storage_index),
536                                            base32.b2a(self.fingerprint))
537         return ret
538
539     def is_readonly(self):
540         return True
541
542     def is_mutable(self):
543         return False
544
545     def get_readonly(self):
546         return self
547
548     def get_verify_cap(self):
549         return self
550
551 class _DirectoryBaseURI(_BaseURI):
552     implements(IURI, IDirnodeURI)
553     def __init__(self, filenode_uri=None):
554         self._filenode_uri = filenode_uri
555
556     def __repr__(self):
557         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
558
559     @classmethod
560     def init_from_string(cls, uri):
561         mo = cls.BASE_STRING_RE.search(uri)
562         if not mo:
563             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
564         bits = uri[mo.end():]
565         fn = cls.INNER_URI_CLASS.init_from_string(
566             cls.INNER_URI_CLASS.BASE_STRING+bits)
567         return cls(fn)
568
569     @classmethod
570     def init_from_human_encoding(cls, uri):
571         mo = cls.BASE_HUMAN_RE.search(uri)
572         if not mo:
573             raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
574         bits = uri[mo.end():]
575         while bits and bits[-1] == '/':
576             bits = bits[:-1]
577         fn = cls.INNER_URI_CLASS.init_from_string(
578             cls.INNER_URI_CLASS.BASE_STRING+urllib.unquote(bits))
579         return cls(fn)
580
581     def to_string(self):
582         fnuri = self._filenode_uri.to_string()
583         mo = re.match(self.INNER_URI_CLASS.BASE_STRING, fnuri)
584         assert mo, fnuri
585         bits = fnuri[mo.end():]
586         return self.BASE_STRING+bits
587
588     def abbrev(self):
589         return self._filenode_uri.to_string().split(':')[2][:5]
590
591     def abbrev_si(self):
592         si = self._filenode_uri.get_storage_index()
593         if si is None:
594             return "<LIT>"
595         return base32.b2a(si)[:5]
596
597     def is_mutable(self):
598         return True
599
600     def get_filenode_cap(self):
601         return self._filenode_uri
602
603     def get_verify_cap(self):
604         return DirectoryURIVerifier(self._filenode_uri.get_verify_cap())
605
606     def get_storage_index(self):
607         return self._filenode_uri.get_storage_index()
608
609 class DirectoryURI(_DirectoryBaseURI):
610     implements(IDirectoryURI)
611
612     BASE_STRING='URI:DIR2:'
613     BASE_STRING_RE=re.compile('^'+BASE_STRING)
614     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2'+SEP)
615     INNER_URI_CLASS=WriteableSSKFileURI
616
617     def __init__(self, filenode_uri=None):
618         if filenode_uri:
619             assert not filenode_uri.is_readonly()
620         _DirectoryBaseURI.__init__(self, filenode_uri)
621
622     def is_readonly(self):
623         return False
624
625     def get_readonly(self):
626         return ReadonlyDirectoryURI(self._filenode_uri.get_readonly())
627
628
629 class ReadonlyDirectoryURI(_DirectoryBaseURI):
630     implements(IReadonlyDirectoryURI)
631
632     BASE_STRING='URI:DIR2-RO:'
633     BASE_STRING_RE=re.compile('^'+BASE_STRING)
634     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-RO'+SEP)
635     INNER_URI_CLASS=ReadonlySSKFileURI
636
637     def __init__(self, filenode_uri=None):
638         if filenode_uri:
639             assert filenode_uri.is_readonly()
640         _DirectoryBaseURI.__init__(self, filenode_uri)
641
642     def is_readonly(self):
643         return True
644
645     def get_readonly(self):
646         return self
647
648
649 class _ImmutableDirectoryBaseURI(_DirectoryBaseURI):
650     def __init__(self, filenode_uri=None):
651         if filenode_uri:
652             assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri
653             assert not filenode_uri.is_mutable()
654         _DirectoryBaseURI.__init__(self, filenode_uri)
655
656     def is_readonly(self):
657         return True
658
659     def is_mutable(self):
660         return False
661
662     def get_readonly(self):
663         return self
664
665
666 class ImmutableDirectoryURI(_ImmutableDirectoryBaseURI):
667     BASE_STRING='URI:DIR2-CHK:'
668     BASE_STRING_RE=re.compile('^'+BASE_STRING)
669     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK'+SEP)
670     INNER_URI_CLASS=CHKFileURI
671
672     def get_verify_cap(self):
673         vcap = self._filenode_uri.get_verify_cap()
674         return ImmutableDirectoryURIVerifier(vcap)
675
676
677 class LiteralDirectoryURI(_ImmutableDirectoryBaseURI):
678     BASE_STRING='URI:DIR2-LIT:'
679     BASE_STRING_RE=re.compile('^'+BASE_STRING)
680     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-LIT'+SEP)
681     INNER_URI_CLASS=LiteralFileURI
682
683     def get_verify_cap(self):
684         # LIT caps have no verifier, since they aren't distributed
685         return None
686
687
688 class MDMFDirectoryURI(_DirectoryBaseURI):
689     implements(IDirectoryURI)
690
691     BASE_STRING='URI:DIR2-MDMF:'
692     BASE_STRING_RE=re.compile('^'+BASE_STRING)
693     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF'+SEP)
694     INNER_URI_CLASS=WriteableMDMFFileURI
695
696     def __init__(self, filenode_uri=None):
697         if filenode_uri:
698             assert not filenode_uri.is_readonly()
699         _DirectoryBaseURI.__init__(self, filenode_uri)
700
701     def is_readonly(self):
702         return False
703
704     def get_readonly(self):
705         return ReadonlyMDMFDirectoryURI(self._filenode_uri.get_readonly())
706
707     def get_verify_cap(self):
708         return MDMFDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
709
710
711 class ReadonlyMDMFDirectoryURI(_DirectoryBaseURI):
712     implements(IReadonlyDirectoryURI)
713
714     BASE_STRING='URI:DIR2-MDMF-RO:'
715     BASE_STRING_RE=re.compile('^'+BASE_STRING)
716     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF-RO'+SEP)
717     INNER_URI_CLASS=ReadonlyMDMFFileURI
718
719     def __init__(self, filenode_uri=None):
720         if filenode_uri:
721             assert filenode_uri.is_readonly()
722         _DirectoryBaseURI.__init__(self, filenode_uri)
723
724     def is_readonly(self):
725         return True
726
727     def get_readonly(self):
728         return self
729
730     def get_verify_cap(self):
731         return MDMFDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
732
733 def wrap_dirnode_cap(filecap):
734     if isinstance(filecap, WriteableSSKFileURI):
735         return DirectoryURI(filecap)
736     if isinstance(filecap, ReadonlySSKFileURI):
737         return ReadonlyDirectoryURI(filecap)
738     if isinstance(filecap, CHKFileURI):
739         return ImmutableDirectoryURI(filecap)
740     if isinstance(filecap, LiteralFileURI):
741         return LiteralDirectoryURI(filecap)
742     if isinstance(filecap, WriteableMDMFFileURI):
743         return MDMFDirectoryURI(filecap)
744     if isinstance(filecap, ReadonlyMDMFFileURI):
745         return ReadonlyMDMFDirectoryURI(filecap)
746     assert False, "cannot interpret as a directory cap: %s" % filecap.__class__
747
748 class MDMFDirectoryURIVerifier(_DirectoryBaseURI):
749     implements(IVerifierURI)
750
751     BASE_STRING='URI:DIR2-MDMF-Verifier:'
752     BASE_STRING_RE=re.compile('^'+BASE_STRING)
753     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF-Verifier'+SEP)
754     INNER_URI_CLASS=MDMFVerifierURI
755
756     def __init__(self, filenode_uri=None):
757         if filenode_uri:
758             assert IVerifierURI.providedBy(filenode_uri)
759         self._filenode_uri = filenode_uri
760
761     def get_filenode_cap(self):
762         return self._filenode_uri
763
764     def is_mutable(self):
765         return False
766
767     def is_readonly(self):
768         return True
769
770     def get_readonly(self):
771         return self
772
773
774 class DirectoryURIVerifier(_DirectoryBaseURI):
775     implements(IVerifierURI)
776
777     BASE_STRING='URI:DIR2-Verifier:'
778     BASE_STRING_RE=re.compile('^'+BASE_STRING)
779     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-Verifier'+SEP)
780     INNER_URI_CLASS=SSKVerifierURI
781
782     def __init__(self, filenode_uri=None):
783         if filenode_uri:
784             assert IVerifierURI.providedBy(filenode_uri)
785         self._filenode_uri = filenode_uri
786
787     def get_filenode_cap(self):
788         return self._filenode_uri
789
790     def is_mutable(self):
791         return False
792
793     def is_readonly(self):
794         return True
795
796     def get_readonly(self):
797         return self
798
799
800 class ImmutableDirectoryURIVerifier(DirectoryURIVerifier):
801     implements(IVerifierURI)
802     BASE_STRING='URI:DIR2-CHK-Verifier:'
803     BASE_STRING_RE=re.compile('^'+BASE_STRING)
804     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK-VERIFIER'+SEP)
805     INNER_URI_CLASS=CHKFileVerifierURI
806
807
808 class UnknownURI:
809     def __init__(self, uri, error=None):
810         self._uri = uri
811         self._error = error
812
813     def to_string(self):
814         return self._uri
815
816     def get_readonly(self):
817         return None
818
819     def get_error(self):
820         return self._error
821
822     def get_verify_cap(self):
823         return None
824
825
826 ALLEGED_READONLY_PREFIX = 'ro.'
827 ALLEGED_IMMUTABLE_PREFIX = 'imm.'
828
829 def from_string(u, deep_immutable=False, name=u"<unknown name>"):
830     if not isinstance(u, str):
831         raise TypeError("unknown URI type: %s.." % str(u)[:100])
832
833     # We allow and check ALLEGED_READONLY_PREFIX or ALLEGED_IMMUTABLE_PREFIX
834     # on all URIs, even though we would only strictly need to do so for caps of
835     # new formats (post Tahoe-LAFS 1.6). URIs that are not consistent with their
836     # prefix are treated as unknown. This should be revisited when we add the
837     # new cap formats. See ticket #833 comment:31.
838     s = u
839     can_be_mutable = can_be_writeable = not deep_immutable
840     if s.startswith(ALLEGED_IMMUTABLE_PREFIX):
841         can_be_mutable = can_be_writeable = False
842         s = s[len(ALLEGED_IMMUTABLE_PREFIX):]
843     elif s.startswith(ALLEGED_READONLY_PREFIX):
844         can_be_writeable = False
845         s = s[len(ALLEGED_READONLY_PREFIX):]
846
847     error = None
848     kind = "cap"
849     try:
850         if s.startswith('URI:CHK:'):
851             return CHKFileURI.init_from_string(s)
852         elif s.startswith('URI:CHK-Verifier:'):
853             return CHKFileVerifierURI.init_from_string(s)
854         elif s.startswith('URI:LIT:'):
855             return LiteralFileURI.init_from_string(s)
856         elif s.startswith('URI:SSK:'):
857             if can_be_writeable:
858                 return WriteableSSKFileURI.init_from_string(s)
859             kind = "URI:SSK file writecap"
860         elif s.startswith('URI:SSK-RO:'):
861             if can_be_mutable:
862                 return ReadonlySSKFileURI.init_from_string(s)
863             kind = "URI:SSK-RO readcap to a mutable file"
864         elif s.startswith('URI:SSK-Verifier:'):
865             return SSKVerifierURI.init_from_string(s)
866         elif s.startswith('URI:MDMF:'):
867             if can_be_writeable:
868                 return WriteableMDMFFileURI.init_from_string(s)
869             kind = "URI:MDMF file writecap"
870         elif s.startswith('URI:MDMF-RO:'):
871             if can_be_mutable:
872                 return ReadonlyMDMFFileURI.init_from_string(s)
873             kind = "URI:MDMF-RO readcap to a mutable file"
874         elif s.startswith('URI:MDMF-Verifier:'):
875             return MDMFVerifierURI.init_from_string(s)
876         elif s.startswith('URI:DIR2:'):
877             if can_be_writeable:
878                 return DirectoryURI.init_from_string(s)
879             kind = "URI:DIR2 directory writecap"
880         elif s.startswith('URI:DIR2-RO:'):
881             if can_be_mutable:
882                 return ReadonlyDirectoryURI.init_from_string(s)
883             kind = "URI:DIR2-RO readcap to a mutable directory"
884         elif s.startswith('URI:DIR2-Verifier:'):
885             return DirectoryURIVerifier.init_from_string(s)
886         elif s.startswith('URI:DIR2-CHK:'):
887             return ImmutableDirectoryURI.init_from_string(s)
888         elif s.startswith('URI:DIR2-CHK-Verifier:'):
889             return ImmutableDirectoryURIVerifier.init_from_string(s)
890         elif s.startswith('URI:DIR2-LIT:'):
891             return LiteralDirectoryURI.init_from_string(s)
892         elif s.startswith('URI:DIR2-MDMF:'):
893             if can_be_writeable:
894                 return MDMFDirectoryURI.init_from_string(s)
895             kind = "URI:DIR2-MDMF directory writecap"
896         elif s.startswith('URI:DIR2-MDMF-RO:'):
897             if can_be_mutable:
898                 return ReadonlyMDMFDirectoryURI.init_from_string(s)
899             kind = "URI:DIR2-MDMF-RO readcap to a mutable directory"
900         elif s.startswith('URI:DIR2-MDMF-Verifier:'):
901             return MDMFDirectoryURIVerifier.init_from_string(s)
902         elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable:
903             # For testing how future writeable caps would behave in read-only contexts.
904             kind = "x-tahoe-future-test-writeable: testing cap"
905         elif s.startswith('x-tahoe-future-test-mutable:') and not can_be_mutable:
906             # For testing how future mutable readcaps would behave in immutable contexts.
907             kind = "x-tahoe-future-test-mutable: testing cap"
908         else:
909             return UnknownURI(u)
910
911         # We fell through because a constraint was not met.
912         # Prefer to report the most specific constraint.
913         if not can_be_mutable:
914             error = MustBeDeepImmutableError(kind + " used in an immutable context", name)
915         else:
916             error = MustBeReadonlyError(kind + " used in a read-only context", name)
917
918     except BadURIError, e:
919         error = e
920
921     return UnknownURI(u, error=error)
922
923 def is_uri(s):
924     try:
925         from_string(s, deep_immutable=False)
926         return True
927     except (TypeError, AssertionError):
928         return False
929
930 def is_literal_file_uri(s):
931     if not isinstance(s, str):
932         return False
933     return (s.startswith('URI:LIT:') or
934             s.startswith(ALLEGED_READONLY_PREFIX + 'URI:LIT:') or
935             s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:LIT:'))
936
937 def has_uri_prefix(s):
938     if not isinstance(s, str):
939         return False
940     return (s.startswith("URI:") or
941             s.startswith(ALLEGED_READONLY_PREFIX + 'URI:') or
942             s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:'))
943
944
945 # These take the same keyword arguments as from_string above.
946
947 def from_string_dirnode(s, **kwargs):
948     u = from_string(s, **kwargs)
949     assert IDirnodeURI.providedBy(u)
950     return u
951
952 registerAdapter(from_string_dirnode, str, IDirnodeURI)
953
954 def from_string_filenode(s, **kwargs):
955     u = from_string(s, **kwargs)
956     assert IFileURI.providedBy(u)
957     return u
958
959 registerAdapter(from_string_filenode, str, IFileURI)
960
961 def from_string_mutable_filenode(s, **kwargs):
962     u = from_string(s, **kwargs)
963     assert IMutableFileURI.providedBy(u)
964     return u
965 registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
966
967 def from_string_verifier(s, **kwargs):
968     u = from_string(s, **kwargs)
969     assert IVerifierURI.providedBy(u)
970     return u
971 registerAdapter(from_string_verifier, str, IVerifierURI)
972
973
974 def pack_extension(data):
975     pieces = []
976     for k in sorted(data.keys()):
977         value = data[k]
978         if isinstance(value, (int, long)):
979             value = "%d" % value
980         assert isinstance(value, str), k
981         assert re.match(r'^[a-zA-Z_\-]+$', k)
982         pieces.append(k + ':' + hashutil.netstring(value))
983     uri_extension = ''.join(pieces)
984     return uri_extension
985
986 def unpack_extension(data):
987     d = {}
988     while data:
989         colon = data.index(':')
990         key = data[:colon]
991         data = data[colon+1:]
992
993         colon = data.index(':')
994         number = data[:colon]
995         length = int(number)
996         data = data[colon+1:]
997
998         value = data[:length]
999         assert data[length] == ','
1000         data = data[length+1:]
1001
1002         d[key] = value
1003
1004     # convert certain things to numbers
1005     for intkey in ('size', 'segment_size', 'num_segments',
1006                    'needed_shares', 'total_shares'):
1007         if intkey in d:
1008             d[intkey] = int(d[intkey])
1009     return d
1010
1011
1012 def unpack_extension_readable(data):
1013     unpacked = unpack_extension(data)
1014     unpacked["UEB_hash"] = hashutil.uri_extension_hash(data)
1015     for k in sorted(unpacked.keys()):
1016         if 'hash' in k:
1017             unpacked[k] = base32.b2a(unpacked[k])
1018     return unpacked
1019