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, IDirectoryURI, IReadonlyDirectoryURI
10 class BadURIError(Exception):
13 # the URI shall be an ascii representation of the file. It shall contain
14 # enough information to retrieve and validate the contents. It shall be
15 # expressed in a limited character set (namely [TODO]).
17 BASE32STR_128bits = '(%s{25}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_3bits)
18 BASE32STR_256bits = '(%s{51}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_1bits)
22 NUMBER_IGNORE='(?:[0-9]+)'
24 # URIs (soon to be renamed "caps") are always allowed to come with a leading
25 # 'http://127.0.0.1:(8123|3456)/uri/' that will be ignored.
26 OPTIONALHTTPLEAD=r'(?:https?://(?:[^:/]+)(?::%s)?/uri/)?' % NUMBER_IGNORE
31 return self.to_string().__hash__()
32 def __eq__(self, them):
33 if isinstance(them, _BaseURI):
34 return self.to_string() == them.to_string()
37 def __ne__(self, them):
38 if isinstance(them, _BaseURI):
39 return self.to_string() != them.to_string()
42 def to_human_encoding(self):
43 return 'http://127.0.0.1:3456/uri/'+self.to_string()
45 def get_storage_index(self):
46 return self.storage_index
48 class CHKFileURI(_BaseURI):
49 implements(IURI, IImmutableFileURI)
51 BASE_STRING='URI:CHK:'
52 STRING_RE=re.compile('^URI:CHK:'+BASE32STR_128bits+':'+
53 BASE32STR_256bits+':'+NUMBER+':'+NUMBER+':'+NUMBER+
55 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'CHK'+SEP+
56 BASE32STR_128bits+SEP+BASE32STR_256bits+SEP+NUMBER+
57 SEP+NUMBER+SEP+NUMBER+'$')
59 def __init__(self, key, uri_extension_hash, needed_shares, total_shares,
62 self.uri_extension_hash = uri_extension_hash
63 self.needed_shares = needed_shares
64 self.total_shares = total_shares
66 self.storage_index = hashutil.storage_index_hash(self.key)
67 if not len(self.storage_index) == 16: # sha256 hash truncated to 128
68 raise BadURIError("storage index must be 16 bytes long")
71 def init_from_human_encoding(cls, uri):
72 mo = cls.HUMAN_RE.search(uri)
74 raise BadURIError("%s doesn't look like a cap" % (uri,))
75 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
76 int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
79 def init_from_string(cls, uri):
80 mo = cls.STRING_RE.search(uri)
82 raise BadURIError("%s doesn't look like a cap" % (uri,))
83 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
84 int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
87 assert isinstance(self.needed_shares, int)
88 assert isinstance(self.total_shares, int)
89 assert isinstance(self.size, (int,long))
91 return ('URI:CHK:%s:%s:%d:%d:%d' %
92 (base32.b2a(self.key),
93 base32.b2a(self.uri_extension_hash),
98 def is_readonly(self):
100 def is_mutable(self):
102 def get_readonly(self):
108 def get_verify_cap(self):
109 return CHKFileVerifierURI(storage_index=self.storage_index,
110 uri_extension_hash=self.uri_extension_hash,
111 needed_shares=self.needed_shares,
112 total_shares=self.total_shares,
115 class CHKFileVerifierURI(_BaseURI):
116 implements(IVerifierURI)
118 STRING_RE=re.compile('^URI:CHK-Verifier:'+BASE32STR_128bits+':'+
119 BASE32STR_256bits+':'+NUMBER+':'+NUMBER+':'+NUMBER)
120 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'CHK-Verifier'+SEP+
121 BASE32STR_128bits+SEP+BASE32STR_256bits+SEP+NUMBER+
122 SEP+NUMBER+SEP+NUMBER)
124 def __init__(self, storage_index, uri_extension_hash,
125 needed_shares, total_shares, size):
126 assert len(storage_index) == 16
127 self.storage_index = storage_index
128 self.uri_extension_hash = uri_extension_hash
129 self.needed_shares = needed_shares
130 self.total_shares = total_shares
134 def init_from_human_encoding(cls, uri):
135 mo = cls.HUMAN_RE.search(uri)
137 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
138 int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
141 def init_from_string(cls, uri):
142 mo = cls.STRING_RE.search(uri)
143 assert mo, (uri, cls, cls.STRING_RE)
144 return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)),
145 int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
148 assert isinstance(self.needed_shares, int)
149 assert isinstance(self.total_shares, int)
150 assert isinstance(self.size, (int,long))
152 return ('URI:CHK-Verifier:%s:%s:%d:%d:%d' %
153 (si_b2a(self.storage_index),
154 base32.b2a(self.uri_extension_hash),
160 class LiteralFileURI(_BaseURI):
161 implements(IURI, IImmutableFileURI)
163 BASE_STRING='URI:LIT:'
164 STRING_RE=re.compile('^URI:LIT:'+base32.BASE32STR_anybytes+'$')
165 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'LIT'+SEP+base32.BASE32STR_anybytes+'$')
167 def __init__(self, data=None):
172 def init_from_human_encoding(cls, uri):
173 mo = cls.HUMAN_RE.search(uri)
175 return cls(base32.a2b(mo.group(1)))
178 def init_from_string(cls, uri):
179 mo = cls.STRING_RE.search(uri)
181 return cls(base32.a2b(mo.group(1)))
184 return 'URI:LIT:%s' % base32.b2a(self.data)
186 def is_readonly(self):
188 def is_mutable(self):
190 def get_readonly(self):
192 def get_storage_index(self):
195 def get_verify_cap(self):
196 # LIT files need no verification, all the data is present in the URI
200 return len(self.data)
202 class WriteableSSKFileURI(_BaseURI):
203 implements(IURI, IMutableFileURI)
205 BASE_STRING='URI:SSK:'
206 STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+
207 BASE32STR_256bits+'$')
208 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK'+SEP+
209 BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
211 def __init__(self, writekey, fingerprint):
212 self.writekey = writekey
213 self.readkey = hashutil.ssk_readkey_hash(writekey)
214 self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
215 assert len(self.storage_index) == 16
216 self.fingerprint = fingerprint
219 def init_from_human_encoding(cls, uri):
220 mo = cls.HUMAN_RE.search(uri)
222 raise BadURIError("'%s' doesn't look like a cap" % (uri,))
223 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
226 def init_from_string(cls, uri):
227 mo = cls.STRING_RE.search(uri)
229 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
230 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
233 assert isinstance(self.writekey, str)
234 assert isinstance(self.fingerprint, str)
235 return 'URI:SSK:%s:%s' % (base32.b2a(self.writekey),
236 base32.b2a(self.fingerprint))
239 return "<%s %s>" % (self.__class__.__name__, self.abbrev())
242 return base32.b2a(self.writekey[:5])
244 return base32.b2a(self.storage_index)[:5]
246 def is_readonly(self):
248 def is_mutable(self):
250 def get_readonly(self):
251 return ReadonlySSKFileURI(self.readkey, self.fingerprint)
252 def get_verify_cap(self):
253 return SSKVerifierURI(self.storage_index, self.fingerprint)
255 class ReadonlySSKFileURI(_BaseURI):
256 implements(IURI, IMutableFileURI)
258 BASE_STRING='URI:SSK-RO:'
259 STRING_RE=re.compile('^URI:SSK-RO:'+BASE32STR_128bits+':'+BASE32STR_256bits+'$')
260 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK-RO'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
262 def __init__(self, readkey, fingerprint):
263 self.readkey = readkey
264 self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
265 assert len(self.storage_index) == 16
266 self.fingerprint = fingerprint
269 def init_from_human_encoding(cls, uri):
270 mo = cls.HUMAN_RE.search(uri)
272 raise BadURIError("'%s' doesn't look like a cap" % (uri,))
273 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
276 def init_from_string(cls, uri):
277 mo = cls.STRING_RE.search(uri)
279 raise BadURIError("'%s' doesn't look like a cap" % (uri,))
280 return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
283 assert isinstance(self.readkey, str)
284 assert isinstance(self.fingerprint, str)
285 return 'URI:SSK-RO:%s:%s' % (base32.b2a(self.readkey),
286 base32.b2a(self.fingerprint))
289 return "<%s %s>" % (self.__class__.__name__, self.abbrev())
292 return base32.b2a(self.readkey[:5])
294 return base32.b2a(self.storage_index)[:5]
296 def is_readonly(self):
298 def is_mutable(self):
300 def get_readonly(self):
302 def get_verify_cap(self):
303 return SSKVerifierURI(self.storage_index, self.fingerprint)
305 class SSKVerifierURI(_BaseURI):
306 implements(IVerifierURI)
308 BASE_STRING='URI:SSK-Verifier:'
309 STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+'$')
310 HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'SSK-Verifier'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+'$')
312 def __init__(self, storage_index, fingerprint):
313 assert len(storage_index) == 16
314 self.storage_index = storage_index
315 self.fingerprint = fingerprint
318 def init_from_human_encoding(cls, uri):
319 mo = cls.HUMAN_RE.search(uri)
321 return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
324 def init_from_string(cls, uri):
325 mo = cls.STRING_RE.search(uri)
326 assert mo, (uri, cls)
327 return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
330 assert isinstance(self.storage_index, str)
331 assert isinstance(self.fingerprint, str)
332 return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index),
333 base32.b2a(self.fingerprint))
335 class _DirectoryBaseURI(_BaseURI):
336 implements(IURI, IDirnodeURI)
337 def __init__(self, filenode_uri=None):
338 self._filenode_uri = filenode_uri
341 return "<%s %s>" % (self.__class__.__name__, self.abbrev())
344 def init_from_string(cls, uri):
345 mo = cls.BASE_STRING_RE.search(uri)
347 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
348 bits = uri[mo.end():]
349 fn = cls.INNER_URI_CLASS.init_from_string(
350 cls.INNER_URI_CLASS.BASE_STRING+bits)
354 def init_from_human_encoding(cls, uri):
355 mo = cls.BASE_HUMAN_RE.search(uri)
357 raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
358 bits = uri[mo.end():]
359 while bits and bits[-1] == '/':
361 fn = cls.INNER_URI_CLASS.init_from_string(
362 cls.INNER_URI_CLASS.BASE_STRING+urllib.unquote(bits))
366 fnuri = self._filenode_uri.to_string()
367 mo = re.match(self.INNER_URI_CLASS.BASE_STRING, fnuri)
369 bits = fnuri[mo.end():]
370 return self.BASE_STRING+bits
373 return self._filenode_uri.to_string().split(':')[2][:5]
375 return base32.b2a(self._filenode_uri.storage_index)[:5]
377 def get_filenode_uri(self):
378 return self._filenode_uri
380 def is_mutable(self):
383 def get_verify_cap(self):
384 return DirectoryURIVerifier(self._filenode_uri.get_verify_cap())
386 def get_storage_index(self):
387 return self._filenode_uri.get_storage_index()
389 class DirectoryURI(_DirectoryBaseURI):
390 implements(IDirectoryURI)
392 BASE_STRING='URI:DIR2:'
393 BASE_STRING_RE=re.compile('^'+BASE_STRING)
394 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2'+SEP)
395 INNER_URI_CLASS=WriteableSSKFileURI
397 def __init__(self, filenode_uri=None):
399 assert not filenode_uri.is_readonly()
400 _DirectoryBaseURI.__init__(self, filenode_uri)
402 def is_readonly(self):
405 def get_readonly(self):
406 return ReadonlyDirectoryURI(self._filenode_uri.get_readonly())
408 class ReadonlyDirectoryURI(_DirectoryBaseURI):
409 implements(IReadonlyDirectoryURI)
411 BASE_STRING='URI:DIR2-RO:'
412 BASE_STRING_RE=re.compile('^'+BASE_STRING)
413 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-RO'+SEP)
414 INNER_URI_CLASS=ReadonlySSKFileURI
416 def __init__(self, filenode_uri=None):
418 assert filenode_uri.is_readonly()
419 _DirectoryBaseURI.__init__(self, filenode_uri)
421 def is_readonly(self):
424 def get_readonly(self):
427 class _ImmutableDirectoryBaseURI(_DirectoryBaseURI):
428 def __init__(self, filenode_uri=None):
430 assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri
431 _DirectoryBaseURI.__init__(self, filenode_uri)
433 def is_mutable(self):
436 def is_readonly(self):
439 def get_readonly(self):
442 class ImmutableDirectoryURI(_ImmutableDirectoryBaseURI):
443 BASE_STRING='URI:DIR2-CHK:'
444 BASE_STRING_RE=re.compile('^'+BASE_STRING)
445 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK'+SEP)
446 INNER_URI_CLASS=CHKFileURI
447 def get_verify_cap(self):
448 vcap = self._filenode_uri.get_verify_cap()
449 return ImmutableDirectoryURIVerifier(vcap)
452 class LiteralDirectoryURI(_ImmutableDirectoryBaseURI):
453 BASE_STRING='URI:DIR2-LIT:'
454 BASE_STRING_RE=re.compile('^'+BASE_STRING)
455 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-LIT'+SEP)
456 INNER_URI_CLASS=LiteralFileURI
457 def __init__(self, data=None):
458 filenode_uri = LiteralFileURI(data)
459 _ImmutableDirectoryBaseURI.__init__(self, filenode_uri)
460 def get_verify_cap(self):
461 # LIT caps have no verifier, since they aren't distributed
465 class DirectoryURIVerifier(_DirectoryBaseURI):
466 implements(IVerifierURI)
468 BASE_STRING='URI:DIR2-Verifier:'
469 BASE_STRING_RE=re.compile('^'+BASE_STRING)
470 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-Verifier'+SEP)
471 INNER_URI_CLASS=SSKVerifierURI
473 def __init__(self, filenode_uri=None):
475 filenode_uri = IVerifierURI(filenode_uri)
476 self._filenode_uri = filenode_uri
478 def get_filenode_uri(self):
479 return self._filenode_uri
481 class ImmutableDirectoryURIVerifier(DirectoryURIVerifier):
482 implements(IVerifierURI)
483 BASE_STRING='URI:DIR2-CHK-Verifier:'
484 BASE_STRING_RE=re.compile('^'+BASE_STRING)
485 BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK-VERIFIER'+SEP)
486 INNER_URI_CLASS=CHKFileVerifierURI
489 def __init__(self, uri):
495 if not isinstance(s, str):
496 raise TypeError("unknown URI type: %s.." % str(s)[:100])
497 elif s.startswith('URI:CHK:'):
498 return CHKFileURI.init_from_string(s)
499 elif s.startswith('URI:CHK-Verifier:'):
500 return CHKFileVerifierURI.init_from_string(s)
501 elif s.startswith('URI:LIT:'):
502 return LiteralFileURI.init_from_string(s)
503 elif s.startswith('URI:SSK:'):
504 return WriteableSSKFileURI.init_from_string(s)
505 elif s.startswith('URI:SSK-RO:'):
506 return ReadonlySSKFileURI.init_from_string(s)
507 elif s.startswith('URI:SSK-Verifier:'):
508 return SSKVerifierURI.init_from_string(s)
509 elif s.startswith('URI:DIR2:'):
510 return DirectoryURI.init_from_string(s)
511 elif s.startswith('URI:DIR2-RO:'):
512 return ReadonlyDirectoryURI.init_from_string(s)
513 elif s.startswith('URI:DIR2-Verifier:'):
514 return DirectoryURIVerifier.init_from_string(s)
515 elif s.startswith('URI:DIR2-CHK:'):
516 return ImmutableDirectoryURI.init_from_string(s)
517 elif s.startswith('URI:DIR2-LIT:'):
518 return LiteralDirectoryURI.init_from_string(s)
525 except (TypeError, AssertionError):
528 def from_string_dirnode(s):
530 assert IDirnodeURI.providedBy(u)
533 registerAdapter(from_string_dirnode, str, IDirnodeURI)
535 def from_string_filenode(s):
537 assert IFileURI.providedBy(u)
540 registerAdapter(from_string_filenode, str, IFileURI)
542 def from_string_mutable_filenode(s):
544 assert IMutableFileURI.providedBy(u)
546 registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
548 def from_string_verifier(s):
550 assert IVerifierURI.providedBy(u)
552 registerAdapter(from_string_verifier, str, IVerifierURI)
555 def pack_extension(data):
557 for k in sorted(data.keys()):
559 if isinstance(value, (int, long)):
561 assert isinstance(value, str), k
562 assert re.match(r'^[a-zA-Z_\-]+$', k)
563 pieces.append(k + ':' + hashutil.netstring(value))
564 uri_extension = ''.join(pieces)
567 def unpack_extension(data):
570 colon = data.index(':')
572 data = data[colon+1:]
574 colon = data.index(':')
575 number = data[:colon]
577 data = data[colon+1:]
579 value = data[:length]
580 assert data[length] == ','
581 data = data[length+1:]
585 # convert certain things to numbers
586 for intkey in ('size', 'segment_size', 'num_segments',
587 'needed_shares', 'total_shares'):
589 d[intkey] = int(d[intkey])
593 def unpack_extension_readable(data):
594 unpacked = unpack_extension(data)
595 unpacked["UEB_hash"] = hashutil.uri_extension_hash(data)
596 for k in sorted(unpacked.keys()):
598 unpacked[k] = base32.b2a(unpacked[k])