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