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