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