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