]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/uri.py
5da7c0a6893cccc9e8449827939f1d07a14fef0d
[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 class DirectoryURIVerifier(_DirectoryBaseURI):
830     implements(IVerifierURI)
831
832     BASE_STRING='URI:DIR2-Verifier:'
833     BASE_STRING_RE=re.compile('^'+BASE_STRING)
834     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-Verifier'+SEP)
835     INNER_URI_CLASS=SSKVerifierURI
836
837     def __init__(self, filenode_uri=None):
838         if filenode_uri:
839             assert IVerifierURI.providedBy(filenode_uri)
840         self._filenode_uri = filenode_uri
841
842     def get_filenode_cap(self):
843         return self._filenode_uri
844
845     def is_mutable(self):
846         return False
847
848
849 class ImmutableDirectoryURIVerifier(DirectoryURIVerifier):
850     implements(IVerifierURI)
851     BASE_STRING='URI:DIR2-CHK-Verifier:'
852     BASE_STRING_RE=re.compile('^'+BASE_STRING)
853     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK-VERIFIER'+SEP)
854     INNER_URI_CLASS=CHKFileVerifierURI
855
856
857 class UnknownURI:
858     def __init__(self, uri, error=None):
859         self._uri = uri
860         self._error = error
861
862     def to_string(self):
863         return self._uri
864
865     def get_readonly(self):
866         return None
867
868     def get_error(self):
869         return self._error
870
871     def get_verify_cap(self):
872         return None
873
874
875 ALLEGED_READONLY_PREFIX = 'ro.'
876 ALLEGED_IMMUTABLE_PREFIX = 'imm.'
877
878 def from_string(u, deep_immutable=False, name=u"<unknown name>"):
879     if not isinstance(u, str):
880         raise TypeError("unknown URI type: %s.." % str(u)[:100])
881
882     # We allow and check ALLEGED_READONLY_PREFIX or ALLEGED_IMMUTABLE_PREFIX
883     # on all URIs, even though we would only strictly need to do so for caps of
884     # new formats (post Tahoe-LAFS 1.6). URIs that are not consistent with their
885     # prefix are treated as unknown. This should be revisited when we add the
886     # new cap formats. See <http://allmydata.org/trac/tahoe/ticket/833#comment:31>.
887     s = u
888     can_be_mutable = can_be_writeable = not deep_immutable
889     if s.startswith(ALLEGED_IMMUTABLE_PREFIX):
890         can_be_mutable = can_be_writeable = False
891         s = s[len(ALLEGED_IMMUTABLE_PREFIX):]
892     elif s.startswith(ALLEGED_READONLY_PREFIX):
893         can_be_writeable = False
894         s = s[len(ALLEGED_READONLY_PREFIX):]
895
896     error = None
897     kind = "cap"
898     try:
899         if s.startswith('URI:CHK:'):
900             return CHKFileURI.init_from_string(s)
901         elif s.startswith('URI:CHK-Verifier:'):
902             return CHKFileVerifierURI.init_from_string(s)
903         elif s.startswith('URI:LIT:'):
904             return LiteralFileURI.init_from_string(s)
905         elif s.startswith('URI:SSK:'):
906             if can_be_writeable:
907                 return WriteableSSKFileURI.init_from_string(s)
908             kind = "URI:SSK file writecap"
909         elif s.startswith('URI:SSK-RO:'):
910             if can_be_mutable:
911                 return ReadonlySSKFileURI.init_from_string(s)
912             kind = "URI:SSK-RO readcap to a mutable file"
913         elif s.startswith('URI:SSK-Verifier:'):
914             return SSKVerifierURI.init_from_string(s)
915         elif s.startswith('URI:MDMF:'):
916             if can_be_writeable:
917                 return WriteableMDMFFileURI.init_from_string(s)
918             kind = "URI:MDMF file writecap"
919         elif s.startswith('URI:MDMF-RO:'):
920             if can_be_mutable:
921                 return ReadonlyMDMFFileURI.init_from_string(s)
922             kind = "URI:MDMF-RO readcap to a mutable file"
923         elif s.startswith('URI:MDMF-Verifier:'):
924             return MDMFVerifierURI.init_from_string(s)
925         elif s.startswith('URI:DIR2:'):
926             if can_be_writeable:
927                 return DirectoryURI.init_from_string(s)
928             kind = "URI:DIR2 directory writecap"
929         elif s.startswith('URI:DIR2-RO:'):
930             if can_be_mutable:
931                 return ReadonlyDirectoryURI.init_from_string(s)
932             kind = "URI:DIR2-RO readcap to a mutable directory"
933         elif s.startswith('URI:DIR2-Verifier:'):
934             return DirectoryURIVerifier.init_from_string(s)
935         elif s.startswith('URI:DIR2-CHK:'):
936             return ImmutableDirectoryURI.init_from_string(s)
937         elif s.startswith('URI:DIR2-CHK-Verifier:'):
938             return ImmutableDirectoryURIVerifier.init_from_string(s)
939         elif s.startswith('URI:DIR2-LIT:'):
940             return LiteralDirectoryURI.init_from_string(s)
941         elif s.startswith('URI:DIR2-MDMF:'):
942             if can_be_writeable:
943                 return MDMFDirectoryURI.init_from_string(s)
944             kind = "URI:DIR2-MDMF directory writecap"
945         elif s.startswith('URI:DIR2-MDMF-RO:'):
946             if can_be_mutable:
947                 return ReadonlyMDMFDirectoryURI.init_from_string(s)
948             kind = "URI:DIR2-MDMF-RO readcap to a mutable directory"
949         elif s.startswith('URI:DIR2-MDMF-Verifier:'):
950             return MDMFDirectoryURIVerifier.init_from_string(s)
951         elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable:
952             # For testing how future writeable caps would behave in read-only contexts.
953             kind = "x-tahoe-future-test-writeable: testing cap"
954         elif s.startswith('x-tahoe-future-test-mutable:') and not can_be_mutable:
955             # For testing how future mutable readcaps would behave in immutable contexts.
956             kind = "x-tahoe-future-test-mutable: testing cap"
957         else:
958             return UnknownURI(u)
959
960         # We fell through because a constraint was not met.
961         # Prefer to report the most specific constraint.
962         if not can_be_mutable:
963             error = MustBeDeepImmutableError(kind + " used in an immutable context", name)
964         else:
965             error = MustBeReadonlyError(kind + " used in a read-only context", name)
966
967     except BadURIError, e:
968         error = e
969
970     return UnknownURI(u, error=error)
971
972 def is_uri(s):
973     try:
974         from_string(s, deep_immutable=False)
975         return True
976     except (TypeError, AssertionError):
977         return False
978
979 def is_literal_file_uri(s):
980     if not isinstance(s, str):
981         return False
982     return (s.startswith('URI:LIT:') or
983             s.startswith(ALLEGED_READONLY_PREFIX + 'URI:LIT:') or
984             s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:LIT:'))
985
986 def has_uri_prefix(s):
987     if not isinstance(s, str):
988         return False
989     return (s.startswith("URI:") or
990             s.startswith(ALLEGED_READONLY_PREFIX + 'URI:') or
991             s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:'))
992
993
994 # These take the same keyword arguments as from_string above.
995
996 def from_string_dirnode(s, **kwargs):
997     u = from_string(s, **kwargs)
998     assert IDirnodeURI.providedBy(u)
999     return u
1000
1001 registerAdapter(from_string_dirnode, str, IDirnodeURI)
1002
1003 def from_string_filenode(s, **kwargs):
1004     u = from_string(s, **kwargs)
1005     assert IFileURI.providedBy(u)
1006     return u
1007
1008 registerAdapter(from_string_filenode, str, IFileURI)
1009
1010 def from_string_mutable_filenode(s, **kwargs):
1011     u = from_string(s, **kwargs)
1012     assert IMutableFileURI.providedBy(u)
1013     return u
1014 registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
1015
1016 def from_string_verifier(s, **kwargs):
1017     u = from_string(s, **kwargs)
1018     assert IVerifierURI.providedBy(u)
1019     return u
1020 registerAdapter(from_string_verifier, str, IVerifierURI)
1021
1022
1023 def pack_extension(data):
1024     pieces = []
1025     for k in sorted(data.keys()):
1026         value = data[k]
1027         if isinstance(value, (int, long)):
1028             value = "%d" % value
1029         assert isinstance(value, str), k
1030         assert re.match(r'^[a-zA-Z_\-]+$', k)
1031         pieces.append(k + ':' + hashutil.netstring(value))
1032     uri_extension = ''.join(pieces)
1033     return uri_extension
1034
1035 def unpack_extension(data):
1036     d = {}
1037     while data:
1038         colon = data.index(':')
1039         key = data[:colon]
1040         data = data[colon+1:]
1041
1042         colon = data.index(':')
1043         number = data[:colon]
1044         length = int(number)
1045         data = data[colon+1:]
1046
1047         value = data[:length]
1048         assert data[length] == ','
1049         data = data[length+1:]
1050
1051         d[key] = value
1052
1053     # convert certain things to numbers
1054     for intkey in ('size', 'segment_size', 'num_segments',
1055                    'needed_shares', 'total_shares'):
1056         if intkey in d:
1057             d[intkey] = int(d[intkey])
1058     return d
1059
1060
1061 def unpack_extension_readable(data):
1062     unpacked = unpack_extension(data)
1063     unpacked["UEB_hash"] = hashutil.uri_extension_hash(data)
1064     for k in sorted(unpacked.keys()):
1065         if 'hash' in k:
1066             unpacked[k] = base32.b2a(unpacked[k])
1067     return unpacked
1068