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