]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/uri.py
Tolerate unknown URI types in directory structures. Part of #683.
[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, INewDirectoryURI, IReadonlyNewDirectoryURI
9
10 # the URI shall be an ascii representation of the file. It shall contain
11 # enough information to retrieve and validate the contents. It shall be
12 # expressed in a limited character set (namely [TODO]).
13
14 BASE32STR_128bits = '(%s{25}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_3bits)
15 BASE32STR_256bits = '(%s{51}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_1bits)
16
17 SEP='(?::|%3A)'
18 NUMBER='([0-9]+)'
19 NUMBER_IGNORE='(?:[0-9]+)'
20
21 # URIs (soon to be renamed "caps") are always allowed to come with a leading
22 # 'http://127.0.0.1:(8123|3456)/uri/' that will be ignored.
23 OPTIONALHTTPLEAD=r'(?:https?://(?:[^:/]+)(?::%s)?/uri/)?' % NUMBER_IGNORE
24
25
26 class _BaseURI:
27     def __hash__(self):
28         return self.to_string().__hash__()
29     def __eq__(self, them):
30         if isinstance(them, _BaseURI):
31             return self.to_string() == them.to_string()
32         else:
33             return False
34     def __ne__(self, them):
35         if isinstance(them, _BaseURI):
36             return self.to_string() != them.to_string()
37         else:
38             return True
39     def to_human_encoding(self):
40         return 'http://127.0.0.1:3456/uri/'+self.to_string()
41
42     def get_storage_index(self):
43         return self.storage_index
44
45 class CHKFileURI(_BaseURI):
46     implements(IURI, IImmutableFileURI)
47
48     STRING_RE=re.compile('^URI:CHK:'+BASE32STR_128bits+':'+
49                          BASE32STR_256bits+':'+NUMBER+':'+NUMBER+':'+NUMBER+
50                          '$')
51     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'CHK'+SEP+
52                      BASE32STR_128bits+SEP+BASE32STR_256bits+SEP+NUMBER+
53                      SEP+NUMBER+SEP+NUMBER+'$')
54
55     def __init__(self, key, uri_extension_hash, needed_shares, total_shares,
56                  size):
57         self.key = key
58         self.uri_extension_hash = uri_extension_hash
59         self.needed_shares = needed_shares
60         self.total_shares = total_shares
61         self.size = size
62         self.storage_index = hashutil.storage_index_hash(self.key)
63         assert len(self.storage_index) == 16
64         self.storage_index = hashutil.storage_index_hash(key)
65         assert len(self.storage_index) == 16 # sha256 hash truncated to 128
66
67     @classmethod
68     def init_from_human_encoding(cls, uri):
69         mo = cls.HUMAN_RE.search(uri)
70         assert mo, uri
71         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
72                    int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
73
74     @classmethod
75     def init_from_string(cls, uri):
76         mo = cls.STRING_RE.search(uri)
77         assert mo, uri
78         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
79                    int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
80
81     def to_string(self):
82         assert isinstance(self.needed_shares, int)
83         assert isinstance(self.total_shares, int)
84         assert isinstance(self.size, (int,long))
85
86         return ('URI:CHK:%s:%s:%d:%d:%d' %
87                 (base32.b2a(self.key),
88                  base32.b2a(self.uri_extension_hash),
89                  self.needed_shares,
90                  self.total_shares,
91                  self.size))
92
93     def is_readonly(self):
94         return True
95     def is_mutable(self):
96         return False
97     def get_readonly(self):
98         return self
99
100     def get_size(self):
101         return self.size
102
103     def get_verify_cap(self):
104         return CHKFileVerifierURI(storage_index=self.storage_index,
105                                   uri_extension_hash=self.uri_extension_hash,
106                                   needed_shares=self.needed_shares,
107                                   total_shares=self.total_shares,
108                                   size=self.size)
109
110 class CHKFileVerifierURI(_BaseURI):
111     implements(IVerifierURI)
112
113     STRING_RE=re.compile('^URI:CHK-Verifier:'+BASE32STR_128bits+':'+
114                          BASE32STR_256bits+':'+NUMBER+':'+NUMBER+':'+NUMBER)
115     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'CHK-Verifier'+SEP+
116                         BASE32STR_128bits+SEP+BASE32STR_256bits+SEP+NUMBER+
117                         SEP+NUMBER+SEP+NUMBER)
118
119     def __init__(self, storage_index, uri_extension_hash,
120                  needed_shares, total_shares, size):
121         assert len(storage_index) == 16
122         self.storage_index = storage_index
123         self.uri_extension_hash = uri_extension_hash
124         self.needed_shares = needed_shares
125         self.total_shares = total_shares
126         self.size = size
127
128     @classmethod
129     def init_from_human_encoding(cls, uri):
130         mo = cls.HUMAN_RE.search(uri)
131         assert mo, uri
132         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
133                    int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
134
135     @classmethod
136     def init_from_string(cls, uri):
137         mo = cls.STRING_RE.search(uri)
138         assert mo, (uri, cls, cls.STRING_RE)
139         return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)),
140                    int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
141
142     def to_string(self):
143         assert isinstance(self.needed_shares, int)
144         assert isinstance(self.total_shares, int)
145         assert isinstance(self.size, (int,long))
146
147         return ('URI:CHK-Verifier:%s:%s:%d:%d:%d' %
148                 (si_b2a(self.storage_index),
149                  base32.b2a(self.uri_extension_hash),
150                  self.needed_shares,
151                  self.total_shares,
152                  self.size))
153
154
155 class LiteralFileURI(_BaseURI):
156     implements(IURI, IImmutableFileURI)
157
158     STRING_RE=re.compile('^URI:LIT:'+base32.BASE32STR_anybytes+'$')
159     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'LIT'+SEP+base32.BASE32STR_anybytes+'$')
160
161     def __init__(self, data=None):
162         if data is not None:
163             self.data = data
164
165     @classmethod
166     def init_from_human_encoding(cls, uri):
167         mo = cls.HUMAN_RE.search(uri)
168         assert mo, uri
169         return cls(base32.a2b(mo.group(1)))
170
171     @classmethod
172     def init_from_string(cls, uri):
173         mo = cls.STRING_RE.search(uri)
174         assert mo, uri
175         return cls(base32.a2b(mo.group(1)))
176
177     def to_string(self):
178         return 'URI:LIT:%s' % base32.b2a(self.data)
179
180     def is_readonly(self):
181         return True
182     def is_mutable(self):
183         return False
184     def get_readonly(self):
185         return self
186     def get_storage_index(self):
187         return None
188
189     def get_verify_cap(self):
190         # LIT files need no verification, all the data is present in the URI
191         return None
192
193     def get_size(self):
194         return len(self.data)
195
196 class WriteableSSKFileURI(_BaseURI):
197     implements(IURI, IMutableFileURI)
198
199     BASE_STRING='URI:SSK:'
200     STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+
201                          BASE32STR_256bits+'$')
202     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK'+SEP+
203                         BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
204
205     def __init__(self, writekey, fingerprint):
206         self.writekey = writekey
207         self.readkey = hashutil.ssk_readkey_hash(writekey)
208         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
209         assert len(self.storage_index) == 16
210         self.fingerprint = fingerprint
211
212     @classmethod
213     def init_from_human_encoding(cls, uri):
214         mo = cls.HUMAN_RE.search(uri)
215         assert mo, uri
216         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
217
218     @classmethod
219     def init_from_string(cls, uri):
220         mo = cls.STRING_RE.search(uri)
221         assert mo, (uri, cls)
222         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
223
224     def to_string(self):
225         assert isinstance(self.writekey, str)
226         assert isinstance(self.fingerprint, str)
227         return 'URI:SSK:%s:%s' % (base32.b2a(self.writekey),
228                                   base32.b2a(self.fingerprint))
229
230     def __repr__(self):
231         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
232
233     def abbrev(self):
234         return base32.b2a(self.writekey[:5])
235     def abbrev_si(self):
236         return base32.b2a(self.storage_index)[:5]
237
238     def is_readonly(self):
239         return False
240     def is_mutable(self):
241         return True
242     def get_readonly(self):
243         return ReadonlySSKFileURI(self.readkey, self.fingerprint)
244     def get_verify_cap(self):
245         return SSKVerifierURI(self.storage_index, self.fingerprint)
246
247 class ReadonlySSKFileURI(_BaseURI):
248     implements(IURI, IMutableFileURI)
249
250     BASE_STRING='URI:SSK-RO:'
251     STRING_RE=re.compile('^URI:SSK-RO:'+BASE32STR_128bits+':'+BASE32STR_256bits+'$')
252     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK-RO'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
253
254     def __init__(self, readkey, fingerprint):
255         self.readkey = readkey
256         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
257         assert len(self.storage_index) == 16
258         self.fingerprint = fingerprint
259
260     @classmethod
261     def init_from_human_encoding(cls, uri):
262         mo = cls.HUMAN_RE.search(uri)
263         assert mo, uri
264         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
265
266     @classmethod
267     def init_from_string(cls, uri):
268         mo = cls.STRING_RE.search(uri)
269         assert mo, uri
270         return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
271
272     def to_string(self):
273         assert isinstance(self.readkey, str)
274         assert isinstance(self.fingerprint, str)
275         return 'URI:SSK-RO:%s:%s' % (base32.b2a(self.readkey),
276                                      base32.b2a(self.fingerprint))
277
278     def __repr__(self):
279         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
280
281     def abbrev(self):
282         return base32.b2a(self.readkey[:5])
283     def abbrev_si(self):
284         return base32.b2a(self.storage_index)[:5]
285
286     def is_readonly(self):
287         return True
288     def is_mutable(self):
289         return True
290     def get_readonly(self):
291         return self
292     def get_verify_cap(self):
293         return SSKVerifierURI(self.storage_index, self.fingerprint)
294
295 class SSKVerifierURI(_BaseURI):
296     implements(IVerifierURI)
297
298     BASE_STRING='URI:SSK-Verifier:'
299     STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'$')
300     HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK-Verifier'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
301
302     def __init__(self, storage_index, fingerprint):
303         assert len(storage_index) == 16
304         self.storage_index = storage_index
305         self.fingerprint = fingerprint
306
307     @classmethod
308     def init_from_human_encoding(cls, uri):
309         mo = cls.HUMAN_RE.search(uri)
310         assert mo, uri
311         return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
312
313     @classmethod
314     def init_from_string(cls, uri):
315         mo = cls.STRING_RE.search(uri)
316         assert mo, (uri, cls)
317         return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
318
319     def to_string(self):
320         assert isinstance(self.storage_index, str)
321         assert isinstance(self.fingerprint, str)
322         return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index),
323                                            base32.b2a(self.fingerprint))
324
325 class _NewDirectoryBaseURI(_BaseURI):
326     implements(IURI, IDirnodeURI)
327     def __init__(self, filenode_uri=None):
328         self._filenode_uri = filenode_uri
329
330     def __repr__(self):
331         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
332
333     @classmethod
334     def init_from_string(cls, uri):
335         mo = cls.BASE_STRING_RE.search(uri)
336         assert mo, (uri, cls)
337         bits = uri[mo.end():]
338         fn = cls.INNER_URI_CLASS.init_from_string(
339             cls.INNER_URI_CLASS.BASE_STRING+bits)
340         return cls(fn)
341
342     @classmethod
343     def init_from_human_encoding(cls, uri):
344         mo = cls.BASE_HUMAN_RE.search(uri)
345         assert mo, (uri, cls)
346         bits = uri[mo.end():]
347         while bits and bits[-1] == '/':
348             bits = bits[:-1]
349         fn = cls.INNER_URI_CLASS.init_from_string(
350             cls.INNER_URI_CLASS.BASE_STRING+urllib.unquote(bits))
351         return cls(fn)
352
353     def to_string(self):
354         fnuri = self._filenode_uri.to_string()
355         mo = re.match(self.INNER_URI_CLASS.BASE_STRING, fnuri)
356         assert mo, fnuri
357         bits = fnuri[mo.end():]
358         return self.BASE_STRING+bits
359
360     def abbrev(self):
361         return self._filenode_uri.to_string().split(':')[2][:5]
362     def abbrev_si(self):
363         return base32.b2a(self._filenode_uri.storage_index)[:5]
364
365     def get_filenode_uri(self):
366         return self._filenode_uri
367
368     def is_mutable(self):
369         return True
370
371     def get_verify_cap(self):
372         return NewDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
373
374     def get_storage_index(self):
375         return self._filenode_uri.get_storage_index()
376
377 class NewDirectoryURI(_NewDirectoryBaseURI):
378     implements(INewDirectoryURI)
379
380     BASE_STRING='URI:DIR2:'
381     BASE_STRING_RE=re.compile('^'+BASE_STRING)
382     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2'+SEP)
383     INNER_URI_CLASS=WriteableSSKFileURI
384
385     def __init__(self, filenode_uri=None):
386         if filenode_uri:
387             assert not filenode_uri.is_readonly()
388         _NewDirectoryBaseURI.__init__(self, filenode_uri)
389
390     def is_readonly(self):
391         return False
392
393     def get_readonly(self):
394         return ReadonlyNewDirectoryURI(self._filenode_uri.get_readonly())
395
396 class ReadonlyNewDirectoryURI(_NewDirectoryBaseURI):
397     implements(IReadonlyNewDirectoryURI)
398
399     BASE_STRING='URI:DIR2-RO:'
400     BASE_STRING_RE=re.compile('^'+BASE_STRING)
401     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-RO'+SEP)
402     INNER_URI_CLASS=ReadonlySSKFileURI
403
404     def __init__(self, filenode_uri=None):
405         if filenode_uri:
406             assert filenode_uri.is_readonly()
407         _NewDirectoryBaseURI.__init__(self, filenode_uri)
408
409     def is_readonly(self):
410         return True
411
412     def get_readonly(self):
413         return self
414
415 class NewDirectoryURIVerifier(_NewDirectoryBaseURI):
416     implements(IVerifierURI)
417
418     BASE_STRING='URI:DIR2-Verifier:'
419     BASE_STRING_RE=re.compile('^'+BASE_STRING)
420     BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-Verifier'+SEP)
421     INNER_URI_CLASS=SSKVerifierURI
422
423     def __init__(self, filenode_uri=None):
424         if filenode_uri:
425             filenode_uri = IVerifierURI(filenode_uri)
426         self._filenode_uri = filenode_uri
427
428     def get_filenode_uri(self):
429         return self._filenode_uri
430
431 class UnknownURI:
432     def __init__(self, uri):
433         self._uri = uri
434     def to_string(self):
435         return self._uri
436
437 def from_string(s):
438     if not isinstance(s, str):
439         raise TypeError("unknown URI type: %s.." % str(s)[:100])
440     elif s.startswith('URI:CHK:'):
441         return CHKFileURI.init_from_string(s)
442     elif s.startswith('URI:CHK-Verifier:'):
443         return CHKFileVerifierURI.init_from_string(s)
444     elif s.startswith('URI:LIT:'):
445         return LiteralFileURI.init_from_string(s)
446     elif s.startswith('URI:SSK:'):
447         return WriteableSSKFileURI.init_from_string(s)
448     elif s.startswith('URI:SSK-RO:'):
449         return ReadonlySSKFileURI.init_from_string(s)
450     elif s.startswith('URI:SSK-Verifier:'):
451         return SSKVerifierURI.init_from_string(s)
452     elif s.startswith('URI:DIR2:'):
453         return NewDirectoryURI.init_from_string(s)
454     elif s.startswith('URI:DIR2-RO:'):
455         return ReadonlyNewDirectoryURI.init_from_string(s)
456     elif s.startswith('URI:DIR2-Verifier:'):
457         return NewDirectoryURIVerifier.init_from_string(s)
458     return UnknownURI(s)
459
460 registerAdapter(from_string, str, IURI)
461
462 def is_uri(s):
463     try:
464         uri = from_string(s)
465         return True
466     except (TypeError, AssertionError):
467         return False
468
469 def from_string_dirnode(s):
470     u = from_string(s)
471     assert IDirnodeURI.providedBy(u)
472     return u
473
474 registerAdapter(from_string_dirnode, str, IDirnodeURI)
475
476 def from_string_filenode(s):
477     u = from_string(s)
478     assert IFileURI.providedBy(u)
479     return u
480
481 registerAdapter(from_string_filenode, str, IFileURI)
482
483 def from_string_mutable_filenode(s):
484     u = from_string(s)
485     assert IMutableFileURI.providedBy(u)
486     return u
487 registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
488
489 def from_string_verifier(s):
490     u = from_string(s)
491     assert IVerifierURI.providedBy(u)
492     return u
493 registerAdapter(from_string_verifier, str, IVerifierURI)
494
495
496 def pack_extension(data):
497     pieces = []
498     for k in sorted(data.keys()):
499         value = data[k]
500         if isinstance(value, (int, long)):
501             value = "%d" % value
502         assert isinstance(value, str), k
503         assert re.match(r'^[a-zA-Z_\-]+$', k)
504         pieces.append(k + ':' + hashutil.netstring(value))
505     uri_extension = ''.join(pieces)
506     return uri_extension
507
508 def unpack_extension(data):
509     d = {}
510     while data:
511         colon = data.index(':')
512         key = data[:colon]
513         data = data[colon+1:]
514
515         colon = data.index(':')
516         number = data[:colon]
517         length = int(number)
518         data = data[colon+1:]
519
520         value = data[:length]
521         assert data[length] == ','
522         data = data[length+1:]
523
524         d[key] = value
525
526     # convert certain things to numbers
527     for intkey in ('size', 'segment_size', 'num_segments',
528                    'needed_shares', 'total_shares'):
529         if intkey in d:
530             d[intkey] = int(d[intkey])
531     return d
532
533
534 def unpack_extension_readable(data):
535     unpacked = unpack_extension(data)
536     unpacked["UEB_hash"] = hashutil.uri_extension_hash(data)
537     for k in sorted(unpacked.keys()):
538         if 'hash' in k:
539             unpacked[k] = base32.b2a(unpacked[k])
540     return unpacked
541