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
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]).
14 BASE32STR_128bits = '(%s{25}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_3bits)
15 BASE32STR_256bits = '(%s{51}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_1bits)
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/)?'
27 return self.to_string().__hash__()
28 def __eq__(self, them):
29 if isinstance(them, _BaseURI):
30 return self.to_string() == them.to_string()
33 def __ne__(self, them):
34 if isinstance(them, _BaseURI):
35 return self.to_string() != them.to_string()
38 def to_human_encoding(self):
39 return 'http://127.0.0.1:3456/uri/'+self.to_string()
41 def get_storage_index(self):
42 return self.storage_index
44 class CHKFileURI(_BaseURI):
45 implements(IURI, IFileURI)
47 STRING_RE=re.compile('^URI:CHK:'+BASE32STR_128bits+':'+
48 BASE32STR_256bits+':'+NUMBER+':'+NUMBER+':'+NUMBER+
50 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'CHK'+SEP+
51 BASE32STR_128bits+SEP+BASE32STR_256bits+SEP+NUMBER+
52 SEP+NUMBER+SEP+NUMBER+'$')
54 def __init__(self, key, uri_extension_hash, needed_shares, total_shares,
57 self.uri_extension_hash = uri_extension_hash
58 self.needed_shares = needed_shares
59 self.total_shares = total_shares
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
67 def init_from_human_encoding(cls, uri):
68 mo = cls.HUMAN_RE.search(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)))
74 def init_from_string(cls, uri):
75 mo = cls.STRING_RE.search(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)))
81 assert isinstance(self.needed_shares, int)
82 assert isinstance(self.total_shares, int)
83 assert isinstance(self.size, (int,long))
85 return ('URI:CHK:%s:%s:%d:%d:%d' %
86 (base32.b2a(self.key),
87 base32.b2a(self.uri_extension_hash),
92 def is_readonly(self):
96 def get_readonly(self):
102 def get_verify_cap(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,
109 class CHKFileVerifierURI(_BaseURI):
110 implements(IVerifierURI)
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)
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
128 def init_from_human_encoding(cls, uri):
129 mo = cls.HUMAN_RE.search(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)))
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)))
142 assert isinstance(self.needed_shares, int)
143 assert isinstance(self.total_shares, int)
144 assert isinstance(self.size, (int,long))
146 return ('URI:CHK-Verifier:%s:%s:%d:%d:%d' %
147 (storage.si_b2a(self.storage_index),
148 base32.b2a(self.uri_extension_hash),
154 class LiteralFileURI(_BaseURI):
155 implements(IURI, IFileURI)
157 STRING_RE=re.compile('^URI:LIT:'+base32.BASE32STR_anybytes+'$')
158 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'LIT'+SEP+base32.BASE32STR_anybytes+'$')
160 def __init__(self, data=None):
165 def init_from_human_encoding(cls, uri):
166 mo = cls.HUMAN_RE.search(uri)
168 return cls(base32.a2b(mo.group(1)))
171 def init_from_string(cls, uri):
172 mo = cls.STRING_RE.search(uri)
174 return cls(base32.a2b(mo.group(1)))
177 return 'URI:LIT:%s' % base32.b2a(self.data)
179 def is_readonly(self):
181 def is_mutable(self):
183 def get_readonly(self):
185 def get_storage_index(self):
188 def get_verify_cap(self):
189 # LIT files need no verification, all the data is present in the URI
193 return len(self.data)
195 class WriteableSSKFileURI(_BaseURI):
196 implements(IURI, IMutableFileURI)
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+'$')
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
212 def init_from_human_encoding(cls, uri):
213 mo = cls.HUMAN_RE.search(uri)
215 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
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)))
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))
230 return "<%s %s>" % (self.__class__.__name__, self.abbrev())
233 return base32.b2a(self.writekey[:5])
235 def is_readonly(self):
237 def is_mutable(self):
239 def get_readonly(self):
240 return ReadonlySSKFileURI(self.readkey, self.fingerprint)
241 def get_verify_cap(self):
242 return SSKVerifierURI(self.storage_index, self.fingerprint)
244 class ReadonlySSKFileURI(_BaseURI):
245 implements(IURI, IMutableFileURI)
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+'$')
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
258 def init_from_human_encoding(cls, uri):
259 mo = cls.HUMAN_RE.search(uri)
261 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
264 def init_from_string(cls, uri):
265 mo = cls.STRING_RE.search(uri)
267 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
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))
276 return "<%s %s>" % (self.__class__.__name__, self.abbrev())
279 return base32.b2a(self.readkey[:5])
281 def is_readonly(self):
283 def is_mutable(self):
285 def get_readonly(self):
287 def get_verify_cap(self):
288 return SSKVerifierURI(self.storage_index, self.fingerprint)
290 class SSKVerifierURI(_BaseURI):
291 implements(IVerifierURI)
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+'$')
297 def __init__(self, storage_index, fingerprint):
298 assert len(storage_index) == 16
299 self.storage_index = storage_index
300 self.fingerprint = fingerprint
303 def init_from_human_encoding(cls, uri):
304 mo = cls.HUMAN_RE.search(uri)
306 return cls(storage.si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
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)))
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))
320 class _NewDirectoryBaseURI(_BaseURI):
321 implements(IURI, IDirnodeURI)
322 def __init__(self, filenode_uri=None):
323 self._filenode_uri = filenode_uri
326 return "<%s %s>" % (self.__class__.__name__, self.abbrev())
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)
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] == '/':
344 fn = cls.INNER_URI_CLASS.init_from_string(
345 cls.INNER_URI_CLASS.BASE_STRING+urllib.unquote(bits))
349 fnuri = self._filenode_uri.to_string()
350 mo = re.match(self.INNER_URI_CLASS.BASE_STRING, fnuri)
352 bits = fnuri[mo.end():]
353 return self.BASE_STRING+bits
356 return self._filenode_uri.to_string().split(':')[2][:5]
358 def get_filenode_uri(self):
359 return self._filenode_uri
361 def is_mutable(self):
364 def get_verify_cap(self):
365 return NewDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
367 def get_storage_index(self):
368 return self._filenode_uri.get_storage_index()
370 class NewDirectoryURI(_NewDirectoryBaseURI):
371 implements(INewDirectoryURI)
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
378 def __init__(self, filenode_uri=None):
380 assert not filenode_uri.is_readonly()
381 _NewDirectoryBaseURI.__init__(self, filenode_uri)
383 def is_readonly(self):
386 def get_readonly(self):
387 return ReadonlyNewDirectoryURI(self._filenode_uri.get_readonly())
389 class ReadonlyNewDirectoryURI(_NewDirectoryBaseURI):
390 implements(IReadonlyNewDirectoryURI)
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
397 def __init__(self, filenode_uri=None):
399 assert filenode_uri.is_readonly()
400 _NewDirectoryBaseURI.__init__(self, filenode_uri)
402 def is_readonly(self):
405 def get_readonly(self):
408 class NewDirectoryURIVerifier(_NewDirectoryBaseURI):
409 implements(IVerifierURI)
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
416 def __init__(self, filenode_uri=None):
418 filenode_uri = IVerifierURI(filenode_uri)
419 self._filenode_uri = filenode_uri
421 def get_filenode_uri(self):
422 return self._filenode_uri
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)
446 raise TypeError("unknown URI type: %s.." % s[:12])
448 registerAdapter(from_string, str, IURI)
454 except (TypeError, AssertionError):
457 def from_string_dirnode(s):
459 assert IDirnodeURI.providedBy(u)
462 registerAdapter(from_string_dirnode, str, IDirnodeURI)
464 def from_string_filenode(s):
466 assert IFileURI.providedBy(u)
469 registerAdapter(from_string_filenode, str, IFileURI)
471 def from_string_mutable_filenode(s):
473 assert IMutableFileURI.providedBy(u)
475 registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
477 def from_string_verifier(s):
479 assert IVerifierURI.providedBy(u)
481 registerAdapter(from_string_verifier, str, IVerifierURI)
484 def pack_extension(data):
486 for k in sorted(data.keys()):
488 if isinstance(value, (int, long)):
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)
496 def unpack_extension(data):
499 colon = data.index(':')
501 data = data[colon+1:]
503 colon = data.index(':')
504 number = data[:colon]
506 data = data[colon+1:]
508 value = data[:length]
509 assert data[length] == ','
510 data = data[length+1:]
514 # convert certain things to numbers
515 for intkey in ('size', 'segment_size', 'num_segments',
516 'needed_shares', 'total_shares'):
518 d[intkey] = int(d[intkey])
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()):
527 unpacked[k] = base32.b2a(unpacked[k])