mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-01-18 18:56:28 +00:00
Prevent mutable objects from being retrieved from an immutable directory, and associated forward-compatibility improvements.
This commit is contained in:
parent
3880486f91
commit
6057bc02cc
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#-----------------------------------------------------------------------------------------------
|
||||
from allmydata.uri import CHKFileURI, DirectoryURI, LiteralFileURI
|
||||
from allmydata.uri import CHKFileURI, DirectoryURI, LiteralFileURI, is_literal_file_uri
|
||||
from allmydata.scripts.common_http import do_http as do_http_req
|
||||
from allmydata.util.hashutil import tagged_hash
|
||||
from allmydata.util.assertutil import precondition
|
||||
@ -335,7 +335,7 @@ class TahoeFuseFile(object):
|
||||
self.fname = self.tfs.cache.tmp_file(os.urandom(20))
|
||||
if self.fnode is None:
|
||||
log('TFF: [%s] open() for write: no file node, creating new File %s' % (self.name, self.fname, ))
|
||||
self.fnode = File(0, 'URI:LIT:')
|
||||
self.fnode = File(0, LiteralFileURI.BASE_STRING)
|
||||
self.fnode.tmp_fname = self.fname # XXX kill this
|
||||
self.parent.add_child(self.name, self.fnode, {})
|
||||
elif hasattr(self.fnode, 'tmp_fname'):
|
||||
@ -362,7 +362,7 @@ class TahoeFuseFile(object):
|
||||
self.fname = self.fnode.tmp_fname
|
||||
log('TFF: reopening(%s) for reading' % self.fname)
|
||||
else:
|
||||
if uri.startswith("URI:LIT") or not self.tfs.async:
|
||||
if is_literal_file_uri(uri) or not self.tfs.async:
|
||||
log('TFF: synchronously fetching file from cache for reading')
|
||||
self.fname = self.tfs.cache.get_file(uri)
|
||||
else:
|
||||
@ -1237,7 +1237,7 @@ class FileCache(object):
|
||||
|
||||
def get_file(self, uri):
|
||||
self.log('get_file(%s)' % (uri,))
|
||||
if uri.startswith("URI:LIT"):
|
||||
if is_literal_file_uri(uri):
|
||||
return self.get_literal(uri)
|
||||
else:
|
||||
return self.get_chk(uri, async=False)
|
||||
|
@ -150,8 +150,12 @@ server prefix. They will be displayed like this:
|
||||
|
||||
=== Child Lookup ===
|
||||
|
||||
Tahoe directories contain named children, just like directories in a regular
|
||||
local filesystem. These children can be either files or subdirectories.
|
||||
Tahoe directories contain named child entries, just like directories in a regular
|
||||
local filesystem. These child entries, called "dirnodes", consist of a name,
|
||||
metadata, a write slot, and a read slot. The write and read slots normally contain
|
||||
a write-cap and read-cap referring to the same object, which can be either a file
|
||||
or a subdirectory. The write slot may be empty (actually, both may be empty,
|
||||
but that is unusual).
|
||||
|
||||
If you have a Tahoe URL that refers to a directory, and want to reference a
|
||||
named child inside it, just append the child name to the URL. For example, if
|
||||
@ -390,6 +394,27 @@ POST /uri?t=mkdir-with-children
|
||||
} } } ]
|
||||
}
|
||||
|
||||
For forward-compatibility, a mutable directory can also contain caps in
|
||||
a format that is unknown to the webapi server. When such caps are retrieved
|
||||
from a mutable directory in a "ro_uri" field, they will be prefixed with
|
||||
the string "ro.", indicating that they must not be decoded without
|
||||
checking that they are read-only. The "ro." prefix must not be stripped
|
||||
off without performing this check. (Future versions of the webapi server
|
||||
will perform it where necessary.)
|
||||
|
||||
If both the "rw_uri" and "ro_uri" fields are present in a given PROPDICT,
|
||||
and the webapi server recognizes the rw_uri as a write cap, then it will
|
||||
reset the ro_uri to the corresponding read cap and discard the original
|
||||
contents of ro_uri (in order to ensure that the two caps correspond to the
|
||||
same object and that the ro_uri is in fact read-only). However this may not
|
||||
happen for caps in a format unknown to the webapi server. Therefore, when
|
||||
writing a directory the webapi client should ensure that the contents
|
||||
of "rw_uri" and "ro_uri" for a given PROPDICT are a consistent
|
||||
(write cap, read cap) pair if possible. If the webapi client only has
|
||||
one cap and does not know whether it is a write cap or read cap, then
|
||||
it is acceptable to set "rw_uri" to that cap and omit "ro_uri". The
|
||||
client must not put a write cap into a "ro_uri" field.
|
||||
|
||||
Note that the webapi-using client application must not provide the
|
||||
"Content-Type: multipart/form-data" header that usually accompanies HTML
|
||||
form submissions, since the body is not formatted this way. Doing so will
|
||||
@ -404,59 +429,113 @@ POST /uri?t=mkdir-immutable
|
||||
|
||||
Like t=mkdir-with-children above, but the new directory will be
|
||||
deep-immutable. This means that the directory itself is immutable, and that
|
||||
it can only contain deep-immutable objects, like immutable files, literal
|
||||
files, and deep-immutable directories. A non-empty request body is
|
||||
mandatory, since after the directory is created, it will not be possible to
|
||||
add more children to it.
|
||||
it can only contain objects that are treated as being deep-immutable, like
|
||||
immutable files, literal files, and deep-immutable directories.
|
||||
|
||||
For forward-compatibility, a deep-immutable directory can also contain caps
|
||||
in a format that is unknown to the webapi server. When such caps are retrieved
|
||||
from a deep-immutable directory in a "ro_uri" field, they will be prefixed
|
||||
with the string "imm.", indicating that they must not be decoded without
|
||||
checking that they are immutable. The "imm." prefix must not be stripped
|
||||
off without performing this check. (Future versions of the webapi server
|
||||
will perform it where necessary.)
|
||||
|
||||
The cap for each child may be given either in the "rw_uri" or "ro_uri"
|
||||
field of the PROPDICT (not both). If a cap is given in the "rw_uri" field,
|
||||
then the webapi server will check that it is an immutable read-cap of a
|
||||
*known* format, and give an error if it is not. If a cap is given in the
|
||||
"ro_uri" field, then the webapi server will still check whether known
|
||||
caps are immutable, but for unknown caps it will simply assume that the
|
||||
cap can be stored, as described above. Note that an attacker would be
|
||||
able to store any cap in an immutable directory, so this check when
|
||||
creating the directory is only to help non-malicious clients to avoid
|
||||
accidentally giving away more authority than intended.
|
||||
|
||||
A non-empty request body is mandatory, since after the directory is created,
|
||||
it will not be possible to add more children to it.
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
|
||||
PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
|
||||
|
||||
Create new directories as necessary to make sure that the named target
|
||||
($DIRCAP/SUBDIRS../SUBDIR) is a directory. This will create additional
|
||||
intermediate directories as necessary. If the named target directory already
|
||||
exists, this will make no changes to it.
|
||||
intermediate mutable directories as necessary. If the named target directory
|
||||
already exists, this will make no changes to it.
|
||||
|
||||
If the final directory is created, it will be empty.
|
||||
|
||||
This will return an error if a blocking file is present at any of the parent
|
||||
names, preventing the server from creating the necessary parent directory.
|
||||
This operation will return an error if a blocking file is present at any of
|
||||
the parent names, preventing the server from creating the necessary parent
|
||||
directory; or if it would require changing an immutable directory.
|
||||
|
||||
The write-cap of the new directory will be returned as the HTTP response
|
||||
body.
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-with-children
|
||||
|
||||
Like above, but if the final directory is created, it will be populated with
|
||||
initial children from the POST request body, as described above in the
|
||||
/uri?t=mkdir-with-children operation.
|
||||
Like /uri?t=mkdir-with-children, but the final directory is created as a
|
||||
child of an existing mutable directory. This will create additional
|
||||
intermediate mutable directories as necessary. If the final directory is
|
||||
created, it will be populated with initial children from the POST request
|
||||
body, as described above.
|
||||
|
||||
This operation will return an error if a blocking file is present at any of
|
||||
the parent names, preventing the server from creating the necessary parent
|
||||
directory; or if it would require changing an immutable directory; or if
|
||||
the immediate parent directory already has a a child named SUBDIR.
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-immutable
|
||||
|
||||
Like above, but the final directory will be deep-immutable, with the
|
||||
children specified as a JSON dictionary in the POST request body.
|
||||
Like /uri?t=mkdir-immutable, but the final directory is created as a child
|
||||
of an existing mutable directory. The final directory will be deep-immutable,
|
||||
and will be populated with the children specified as a JSON dictionary in
|
||||
the POST request body.
|
||||
|
||||
In Tahoe 1.6 this operation creates intermediate mutable directories if
|
||||
necessary, but that behaviour should not be relied on; see ticket #920.
|
||||
|
||||
This operation will return an error if the parent directory is immutable,
|
||||
or already has a child named SUBDIR.
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME
|
||||
|
||||
Create a new empty directory and attach it to the given existing directory.
|
||||
This will create additional intermediate directories as necessary.
|
||||
Create a new empty mutable directory and attach it to the given existing
|
||||
directory. This will create additional intermediate directories as necessary.
|
||||
|
||||
The URL of this form points to the parent of the bottom-most new directory,
|
||||
whereas the previous form has a URL that points directly to the bottom-most
|
||||
new directory.
|
||||
This operation will return an error if a blocking file is present at any of
|
||||
the parent names, preventing the server from creating the necessary parent
|
||||
directory, or if it would require changing any immutable directory.
|
||||
|
||||
The URL of this operation points to the parent of the bottommost new directory,
|
||||
whereas the /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir operation above has a URL
|
||||
that points directly to the bottommost new directory.
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME
|
||||
|
||||
As above, but the new directory will be populated with initial children via
|
||||
the POST request body, as described in /uri?t=mkdir-with-children above.
|
||||
Like /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME, but the new directory will
|
||||
be populated with initial children via the POST request body. This command
|
||||
will create additional intermediate mutable directories as necessary.
|
||||
|
||||
This operation will return an error if a blocking file is present at any of
|
||||
the parent names, preventing the server from creating the necessary parent
|
||||
directory; or if it would require changing an immutable directory; or if
|
||||
the immediate parent directory already has a a child named NAME.
|
||||
|
||||
Note that the name= argument must be passed as a queryarg, because the POST
|
||||
request body is used for the initial children JSON.
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-immutable&name=NAME
|
||||
|
||||
As above, but the new directory will be deep-immutable, with the children
|
||||
specified as a JSON dictionary in the POST request body. Again, the name=
|
||||
argument must be passed as a queryarg.
|
||||
Like /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME, but the
|
||||
final directory will be deep-immutable. The children are specified as a
|
||||
JSON dictionary in the POST request body. Again, the name= argument must be
|
||||
passed as a queryarg.
|
||||
|
||||
In Tahoe 1.6 this operation creates intermediate mutable directories if
|
||||
necessary, but that behaviour should not be relied on; see ticket #920.
|
||||
|
||||
This operation will return an error if the parent directory is immutable,
|
||||
or already has a child named NAME.
|
||||
|
||||
=== Get Information About A File Or Directory (as JSON) ===
|
||||
|
||||
@ -679,7 +758,9 @@ POST /uri/$DIRCAP/[SUBDIRS..]?t=set-children (Tahoe >= v1.6)
|
||||
"childinfo" is a dictionary that contains "rw_uri", "ro_uri", and
|
||||
"metadata" keys. You can take the output of "GET /uri/$DIRCAP1?t=json" and
|
||||
use it as the input to "POST /uri/$DIRCAP2?t=set_children" to make DIR2
|
||||
look very much like DIR1.
|
||||
look very much like DIR1 (except for any existing children of DIR2 that
|
||||
were not overwritten, and any existing "tahoe" metadata keys as described
|
||||
below).
|
||||
|
||||
When the set_children request contains a child name that already exists in
|
||||
the target directory, this command defaults to overwriting that child with
|
||||
@ -880,9 +961,9 @@ POST /uri?t=upload
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]?t=upload
|
||||
|
||||
This uploads a file, and attaches it as a new child of the given directory.
|
||||
The file must be provided as the "file" field of an HTML encoded form body,
|
||||
produced in response to an HTML form like this:
|
||||
This uploads a file, and attaches it as a new child of the given directory,
|
||||
which must be mutable. The file must be provided as the "file" field of an
|
||||
HTML-encoded form body, produced in response to an HTML form like this:
|
||||
<form action="." method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="t" value="upload" />
|
||||
<input type="file" name="file" />
|
||||
@ -925,9 +1006,10 @@ POST /uri/$DIRCAP/[SUBDIRS../]?t=upload
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=upload
|
||||
|
||||
This also uploads a file and attaches it as a new child of the given
|
||||
directory. It is a slight variant of the previous operation, as the URL
|
||||
refers to the target file rather than the parent directory. It is otherwise
|
||||
identical: this accepts mutable= and when_done= arguments too.
|
||||
directory, which must be mutable. It is a slight variant of the previous
|
||||
operation, as the URL refers to the target file rather than the parent
|
||||
directory. It is otherwise identical: this accepts mutable= and when_done=
|
||||
arguments too.
|
||||
|
||||
POST /uri/$FILECAP?t=upload
|
||||
|
||||
@ -955,20 +1037,21 @@ POST /uri/$DIRCAP/[SUBDIRS../]?t=uri&name=CHILDNAME&uri=CHILDCAP
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]?t=delete&name=CHILDNAME
|
||||
|
||||
This instructs the node to delete a child object (file or subdirectory) from
|
||||
the given directory. Note that the entire subtree is removed. This is
|
||||
somewhat like "rm -rf" (from the point of view of the parent), but other
|
||||
references into the subtree will see that the child subdirectories are not
|
||||
modified by this operation. Only the link from the given directory to its
|
||||
child is severed.
|
||||
This instructs the node to remove a child object (file or subdirectory) from
|
||||
the given directory, which must be mutable. Note that the entire subtree is
|
||||
unlinked from the parent. Unlike deleting a subdirectory in a UNIX local
|
||||
filesystem, the subtree need not be empty; if it isn't, then other references
|
||||
into the subtree will see that the child subdirectories are not modified by
|
||||
this operation. Only the link from the given directory to its child is severed.
|
||||
|
||||
=== Renaming A Child ===
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]?t=rename&from_name=OLD&to_name=NEW
|
||||
|
||||
This instructs the node to rename a child of the given directory. This is
|
||||
exactly the same as removing the child, then adding the same child-cap under
|
||||
the new name. This operation cannot move the child to a different directory.
|
||||
This instructs the node to rename a child of the given directory, which must
|
||||
be mutable. This has a similar effect to removing the child, then adding the
|
||||
same child-cap under the new name, except that it preserves metadata. This
|
||||
operation cannot move the child to a different directory.
|
||||
|
||||
This operation will replace any existing child of the new name, making it
|
||||
behave like the UNIX "mv -f" command.
|
||||
@ -1164,9 +1247,11 @@ POST $URL?t=stream-deep-check
|
||||
|
||||
"path": a list of strings, with the path that is traversed to reach the
|
||||
object
|
||||
"cap": a writecap for the file or directory, if available, else a readcap
|
||||
"verifycap": a verifycap for the file or directory
|
||||
"repaircap": the weakest cap which can still be used to repair the object
|
||||
"cap": a write-cap URI for the file or directory, if available, else a
|
||||
read-cap URI
|
||||
"verifycap": a verify-cap URI for the file or directory
|
||||
"repaircap": an URI for the weakest cap that can still be used to repair
|
||||
the object
|
||||
"storage-index": a base32 storage index for the object
|
||||
"check-results": a copy of the dictionary which would be returned by
|
||||
t=check&output=json, with three top-level keys:
|
||||
@ -1408,9 +1493,11 @@ POST $URL?t=stream-manifest
|
||||
|
||||
"path": a list of strings, with the path that is traversed to reach the
|
||||
object
|
||||
"cap": a writecap for the file or directory, if available, else a readcap
|
||||
"verifycap": a verifycap for the file or directory
|
||||
"repaircap": the weakest cap which can still be used to repair the object
|
||||
"cap": a write-cap URI for the file or directory, if available, else a
|
||||
read-cap URI
|
||||
"verifycap": a verify-cap URI for the file or directory
|
||||
"repaircap": an URI for the weakest cap that can still be used to repair
|
||||
the object
|
||||
"storage-index": a base32 storage index for the object
|
||||
|
||||
Note that non-distributed files (i.e. LIT files) will have values of None
|
||||
@ -1641,6 +1728,17 @@ the child, then those kinds of mistakes just can't happen. Note that both the
|
||||
child's name and the child's URI are included in the results of listing the
|
||||
parent directory, so it isn't any harder to use the URI for this purpose.
|
||||
|
||||
The read and write caps in a given directory node are separate URIs, and
|
||||
can't be assumed to point to the same object even if they were retrieved in
|
||||
the same operation (although the webapi server attempts to ensure this
|
||||
in most cases). If you need to rely on that property, you should explicitly
|
||||
verify it. More generally, you should not make assumptions about the
|
||||
internal consistency of the contents of mutable directories. As a result
|
||||
of the signatures on mutable object versions, it is guaranteed that a given
|
||||
version was written in a single update, but -- as in the case of a file --
|
||||
the contents may have been chosen by a malicious writer in a way that is
|
||||
designed to confuse applications that rely on their consistency.
|
||||
|
||||
In general, use names if you want "whatever object (whether file or
|
||||
directory) is found by following this name (or sequence of names) when my
|
||||
request reaches the server". Use URIs if you want "this particular object".
|
||||
|
@ -471,13 +471,16 @@ class Client(node.Node, pollmixin.PollMixin):
|
||||
# dirnodes. The first takes a URI and produces a filenode or (new-style)
|
||||
# dirnode. The other three create brand-new filenodes/dirnodes.
|
||||
|
||||
def create_node_from_uri(self, writecap, readcap=None):
|
||||
# this returns synchronously.
|
||||
return self.nodemaker.create_from_cap(writecap, readcap)
|
||||
def create_node_from_uri(self, write_uri, read_uri=None, deep_immutable=False, name="<unknown name>"):
|
||||
# This returns synchronously.
|
||||
# Note that it does *not* validate the write_uri and read_uri; instead we
|
||||
# may get an opaque node if there were any problems.
|
||||
return self.nodemaker.create_from_cap(write_uri, read_uri, deep_immutable=deep_immutable, name=name)
|
||||
|
||||
def create_dirnode(self, initial_children={}):
|
||||
d = self.nodemaker.create_new_mutable_directory(initial_children)
|
||||
return d
|
||||
|
||||
def create_immutable_dirnode(self, children, convergence=None):
|
||||
return self.nodemaker.create_immutable_directory(children, convergence)
|
||||
|
||||
|
@ -5,7 +5,7 @@ from twisted.application import service
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.interfaces import IConsumer
|
||||
from foolscap.api import Referenceable
|
||||
from allmydata.interfaces import RIControlClient
|
||||
from allmydata.interfaces import RIControlClient, IFileNode
|
||||
from allmydata.util import fileutil, mathutil
|
||||
from allmydata.immutable import upload
|
||||
from twisted.python import log
|
||||
@ -67,7 +67,9 @@ class ControlServer(Referenceable, service.Service):
|
||||
return d
|
||||
|
||||
def remote_download_from_uri_to_file(self, uri, filename):
|
||||
filenode = self.parent.create_node_from_uri(uri)
|
||||
filenode = self.parent.create_node_from_uri(uri, name=filename)
|
||||
if not IFileNode.providedBy(filenode):
|
||||
raise AssertionError("The URI does not reference a file.")
|
||||
c = FileWritingConsumer(filename)
|
||||
d = filenode.read(c)
|
||||
d.addCallback(lambda res: filename)
|
||||
@ -199,6 +201,8 @@ class SpeedTest:
|
||||
if i >= self.count:
|
||||
return
|
||||
n = self.parent.create_node_from_uri(self.uris[i])
|
||||
if not IFileNode.providedBy(n):
|
||||
raise AssertionError("The URI does not reference a file.")
|
||||
if n.is_mutable():
|
||||
d1 = n.download_best_version()
|
||||
else:
|
||||
|
@ -5,13 +5,13 @@ from zope.interface import implements
|
||||
from twisted.internet import defer
|
||||
from foolscap.api import fireEventually
|
||||
import simplejson
|
||||
from allmydata.mutable.common import NotMutableError
|
||||
from allmydata.mutable.common import NotWriteableError
|
||||
from allmydata.mutable.filenode import MutableFileNode
|
||||
from allmydata.unknown import UnknownNode
|
||||
from allmydata.unknown import UnknownNode, strip_prefix_for_ro
|
||||
from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
|
||||
IImmutableFileNode, IMutableFileNode, \
|
||||
ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
|
||||
CannotPackUnknownNodeError
|
||||
MustBeDeepImmutableError, CapConstraintError
|
||||
from allmydata.check_results import DeepCheckResults, \
|
||||
DeepCheckAndRepairResults
|
||||
from allmydata.monitor import Monitor
|
||||
@ -40,6 +40,7 @@ class Deleter:
|
||||
new_contents = self.node._pack_contents(children)
|
||||
return new_contents
|
||||
|
||||
|
||||
class MetadataSetter:
|
||||
def __init__(self, node, name, metadata):
|
||||
self.node = node
|
||||
@ -75,6 +76,11 @@ class Adder:
|
||||
for (name, (child, new_metadata)) in self.entries.iteritems():
|
||||
precondition(isinstance(name, unicode), name)
|
||||
precondition(IFilesystemNode.providedBy(child), child)
|
||||
|
||||
# Strictly speaking this is redundant because we would raise the
|
||||
# error again in pack_children.
|
||||
child.raise_error()
|
||||
|
||||
if name in children:
|
||||
if not self.overwrite:
|
||||
raise ExistingChildError("child '%s' already exists" % name)
|
||||
@ -123,25 +129,21 @@ class Adder:
|
||||
new_contents = self.node._pack_contents(children)
|
||||
return new_contents
|
||||
|
||||
def _encrypt_rwcap(filenode, rwcap):
|
||||
assert isinstance(rwcap, str)
|
||||
def _encrypt_rw_uri(filenode, rw_uri):
|
||||
assert isinstance(rw_uri, str)
|
||||
writekey = filenode.get_writekey()
|
||||
if not writekey:
|
||||
return ""
|
||||
salt = hashutil.mutable_rwcap_salt_hash(rwcap)
|
||||
salt = hashutil.mutable_rwcap_salt_hash(rw_uri)
|
||||
key = hashutil.mutable_rwcap_key_hash(salt, writekey)
|
||||
cryptor = AES(key)
|
||||
crypttext = cryptor.process(rwcap)
|
||||
crypttext = cryptor.process(rw_uri)
|
||||
mac = hashutil.hmac(key, salt + crypttext)
|
||||
assert len(mac) == 32
|
||||
return salt + crypttext + mac
|
||||
# The MAC is not checked by readers in Tahoe >= 1.3.0, but we still
|
||||
# produce it for the sake of older readers.
|
||||
|
||||
class MustBeDeepImmutable(Exception):
|
||||
"""You tried to add a non-deep-immutable node to a deep-immutable
|
||||
directory."""
|
||||
|
||||
def pack_children(filenode, children, deep_immutable=False):
|
||||
"""Take a dict that maps:
|
||||
children[unicode_name] = (IFileSystemNode, metadata_dict)
|
||||
@ -152,7 +154,7 @@ def pack_children(filenode, children, deep_immutable=False):
|
||||
time.
|
||||
|
||||
If deep_immutable is True, I will require that all my children are deeply
|
||||
immutable, and will raise a MustBeDeepImmutable exception if not.
|
||||
immutable, and will raise a MustBeDeepImmutableError if not.
|
||||
"""
|
||||
|
||||
has_aux = isinstance(children, AuxValueDict)
|
||||
@ -161,25 +163,29 @@ def pack_children(filenode, children, deep_immutable=False):
|
||||
assert isinstance(name, unicode)
|
||||
entry = None
|
||||
(child, metadata) = children[name]
|
||||
if deep_immutable and child.is_mutable():
|
||||
# TODO: consider adding IFileSystemNode.is_deep_immutable()
|
||||
raise MustBeDeepImmutable("child '%s' is mutable" % (name,))
|
||||
child.raise_error()
|
||||
if deep_immutable and not child.is_allowed_in_immutable_directory():
|
||||
raise MustBeDeepImmutableError("child '%s' is not allowed in an immutable directory" % (name,), name)
|
||||
if has_aux:
|
||||
entry = children.get_aux(name)
|
||||
if not entry:
|
||||
assert IFilesystemNode.providedBy(child), (name,child)
|
||||
assert isinstance(metadata, dict)
|
||||
rwcap = child.get_uri() # might be RO if the child is not writeable
|
||||
if rwcap is None:
|
||||
rwcap = ""
|
||||
assert isinstance(rwcap, str), rwcap
|
||||
rocap = child.get_readonly_uri()
|
||||
if rocap is None:
|
||||
rocap = ""
|
||||
assert isinstance(rocap, str), rocap
|
||||
rw_uri = child.get_write_uri()
|
||||
if rw_uri is None:
|
||||
rw_uri = ""
|
||||
assert isinstance(rw_uri, str), rw_uri
|
||||
|
||||
# should be prevented by MustBeDeepImmutableError check above
|
||||
assert not (rw_uri and deep_immutable)
|
||||
|
||||
ro_uri = child.get_readonly_uri()
|
||||
if ro_uri is None:
|
||||
ro_uri = ""
|
||||
assert isinstance(ro_uri, str), ro_uri
|
||||
entry = "".join([netstring(name.encode("utf-8")),
|
||||
netstring(rocap),
|
||||
netstring(_encrypt_rwcap(filenode, rwcap)),
|
||||
netstring(strip_prefix_for_ro(ro_uri, deep_immutable)),
|
||||
netstring(_encrypt_rw_uri(filenode, rw_uri)),
|
||||
netstring(simplejson.dumps(metadata))])
|
||||
entries.append(netstring(entry))
|
||||
return "".join(entries)
|
||||
@ -230,38 +236,64 @@ class DirectoryNode:
|
||||
plaintext = cryptor.process(crypttext)
|
||||
return plaintext
|
||||
|
||||
def _create_node(self, rwcap, rocap):
|
||||
return self._nodemaker.create_from_cap(rwcap, rocap)
|
||||
def _create_and_validate_node(self, rw_uri, ro_uri, name):
|
||||
node = self._nodemaker.create_from_cap(rw_uri, ro_uri,
|
||||
deep_immutable=not self.is_mutable(),
|
||||
name=name)
|
||||
node.raise_error()
|
||||
return node
|
||||
|
||||
def _unpack_contents(self, data):
|
||||
# the directory is serialized as a list of netstrings, one per child.
|
||||
# Each child is serialized as a list of four netstrings: (name,
|
||||
# rocap, rwcap, metadata), in which the name,rocap,metadata are in
|
||||
# cleartext. The 'name' is UTF-8 encoded. The rwcap is formatted as:
|
||||
# pack("16ss32s", iv, AES(H(writekey+iv), plaintextrwcap), mac)
|
||||
# Each child is serialized as a list of four netstrings: (name, ro_uri,
|
||||
# rwcapdata, metadata), in which the name, ro_uri, metadata are in
|
||||
# cleartext. The 'name' is UTF-8 encoded. The rwcapdata is formatted as:
|
||||
# pack("16ss32s", iv, AES(H(writekey+iv), plaintext_rw_uri), mac)
|
||||
assert isinstance(data, str), (repr(data), type(data))
|
||||
# an empty directory is serialized as an empty string
|
||||
if data == "":
|
||||
return AuxValueDict()
|
||||
writeable = not self.is_readonly()
|
||||
mutable = self.is_mutable()
|
||||
children = AuxValueDict()
|
||||
position = 0
|
||||
while position < len(data):
|
||||
entries, position = split_netstring(data, 1, position)
|
||||
entry = entries[0]
|
||||
(name, rocap, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
|
||||
(name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
|
||||
if not mutable and len(rwcapdata) > 0:
|
||||
raise ValueError("the rwcapdata field of a dirnode in an immutable directory was not empty")
|
||||
name = name.decode("utf-8")
|
||||
rwcap = None
|
||||
rw_uri = ""
|
||||
if writeable:
|
||||
rwcap = self._decrypt_rwcapdata(rwcapdata)
|
||||
if not rwcap:
|
||||
rwcap = None # rwcap is None or a non-empty string
|
||||
if not rocap:
|
||||
rocap = None # rocap is None or a non-empty string
|
||||
child = self._create_node(rwcap, rocap)
|
||||
metadata = simplejson.loads(metadata_s)
|
||||
assert isinstance(metadata, dict)
|
||||
children.set_with_aux(name, (child, metadata), auxilliary=entry)
|
||||
rw_uri = self._decrypt_rwcapdata(rwcapdata)
|
||||
|
||||
# Since the encryption uses CTR mode, it currently leaks the length of the
|
||||
# plaintext rw_uri -- and therefore whether it is present, i.e. whether the
|
||||
# dirnode is writeable (ticket #925). By stripping spaces in Tahoe >= 1.6.0,
|
||||
# we may make it easier for future versions to plug this leak.
|
||||
# ro_uri is treated in the same way for consistency.
|
||||
# rw_uri and ro_uri will be either None or a non-empty string.
|
||||
|
||||
rw_uri = rw_uri.strip(' ') or None
|
||||
ro_uri = ro_uri.strip(' ') or None
|
||||
|
||||
try:
|
||||
child = self._create_and_validate_node(rw_uri, ro_uri, name)
|
||||
if mutable or child.is_allowed_in_immutable_directory():
|
||||
metadata = simplejson.loads(metadata_s)
|
||||
assert isinstance(metadata, dict)
|
||||
children[name] = (child, metadata)
|
||||
children.set_with_aux(name, (child, metadata), auxilliary=entry)
|
||||
else:
|
||||
log.msg(format="mutable cap for child '%(name)s' unpacked from an immutable directory",
|
||||
name=name.encode("utf-8"),
|
||||
facility="tahoe.webish", level=log.UNUSUAL)
|
||||
except CapConstraintError, e:
|
||||
log.msg(format="unmet constraint on cap for child '%(name)s' unpacked from a directory:\n"
|
||||
"%(message)s", message=e.args[0], name=name.encode("utf-8"),
|
||||
facility="tahoe.webish", level=log.UNUSUAL)
|
||||
|
||||
return children
|
||||
|
||||
def _pack_contents(self, children):
|
||||
@ -270,21 +302,39 @@ class DirectoryNode:
|
||||
|
||||
def is_readonly(self):
|
||||
return self._node.is_readonly()
|
||||
|
||||
def is_mutable(self):
|
||||
return self._node.is_mutable()
|
||||
|
||||
def is_unknown(self):
|
||||
return False
|
||||
|
||||
def is_allowed_in_immutable_directory(self):
|
||||
return not self._node.is_mutable()
|
||||
|
||||
def raise_error(self):
|
||||
pass
|
||||
|
||||
def get_uri(self):
|
||||
return self._uri.to_string()
|
||||
|
||||
def get_write_uri(self):
|
||||
if self.is_readonly():
|
||||
return None
|
||||
return self._uri.to_string()
|
||||
|
||||
def get_readonly_uri(self):
|
||||
return self._uri.get_readonly().to_string()
|
||||
|
||||
def get_cap(self):
|
||||
return self._uri
|
||||
|
||||
def get_readcap(self):
|
||||
return self._uri.get_readonly()
|
||||
|
||||
def get_verify_cap(self):
|
||||
return self._uri.get_verify_cap()
|
||||
|
||||
def get_repair_cap(self):
|
||||
if self._node.is_readonly():
|
||||
return None # readonly (mutable) dirnodes are not yet repairable
|
||||
@ -350,7 +400,7 @@ class DirectoryNode:
|
||||
def set_metadata_for(self, name, metadata):
|
||||
assert isinstance(name, unicode)
|
||||
if self.is_readonly():
|
||||
return defer.fail(NotMutableError())
|
||||
return defer.fail(NotWriteableError())
|
||||
assert isinstance(metadata, dict)
|
||||
s = MetadataSetter(self, name, metadata)
|
||||
d = self._node.modify(s.modify)
|
||||
@ -398,14 +448,10 @@ class DirectoryNode:
|
||||
precondition(isinstance(name, unicode), name)
|
||||
precondition(isinstance(writecap, (str,type(None))), writecap)
|
||||
precondition(isinstance(readcap, (str,type(None))), readcap)
|
||||
child_node = self._create_node(writecap, readcap)
|
||||
if isinstance(child_node, UnknownNode):
|
||||
# don't be willing to pack unknown nodes: we might accidentally
|
||||
# put some write-authority into the rocap slot because we don't
|
||||
# know how to diminish the URI they gave us. We don't even know
|
||||
# if they gave us a readcap or a writecap.
|
||||
msg = "cannot pack unknown node as child %s" % str(name)
|
||||
raise CannotPackUnknownNodeError(msg)
|
||||
|
||||
# We now allow packing unknown nodes, provided they are valid
|
||||
# for this type of directory.
|
||||
child_node = self._create_and_validate_node(writecap, readcap, name)
|
||||
d = self.set_node(name, child_node, metadata, overwrite)
|
||||
d.addCallback(lambda res: child_node)
|
||||
return d
|
||||
@ -423,10 +469,10 @@ class DirectoryNode:
|
||||
writecap, readcap, metadata = e
|
||||
precondition(isinstance(writecap, (str,type(None))), writecap)
|
||||
precondition(isinstance(readcap, (str,type(None))), readcap)
|
||||
child_node = self._create_node(writecap, readcap)
|
||||
if isinstance(child_node, UnknownNode):
|
||||
msg = "cannot pack unknown node as child %s" % str(name)
|
||||
raise CannotPackUnknownNodeError(msg)
|
||||
|
||||
# We now allow packing unknown nodes, provided they are valid
|
||||
# for this type of directory.
|
||||
child_node = self._create_and_validate_node(writecap, readcap, name)
|
||||
a.set_node(name, child_node, metadata)
|
||||
d = self._node.modify(a.modify)
|
||||
d.addCallback(lambda ign: self)
|
||||
@ -439,12 +485,12 @@ class DirectoryNode:
|
||||
same name.
|
||||
|
||||
If this directory node is read-only, the Deferred will errback with a
|
||||
NotMutableError."""
|
||||
NotWriteableError."""
|
||||
|
||||
precondition(IFilesystemNode.providedBy(child), child)
|
||||
|
||||
if self.is_readonly():
|
||||
return defer.fail(NotMutableError())
|
||||
return defer.fail(NotWriteableError())
|
||||
assert isinstance(name, unicode)
|
||||
assert IFilesystemNode.providedBy(child), child
|
||||
a = Adder(self, overwrite=overwrite)
|
||||
@ -456,7 +502,7 @@ class DirectoryNode:
|
||||
def set_nodes(self, entries, overwrite=True):
|
||||
precondition(isinstance(entries, dict), entries)
|
||||
if self.is_readonly():
|
||||
return defer.fail(NotMutableError())
|
||||
return defer.fail(NotWriteableError())
|
||||
a = Adder(self, entries, overwrite=overwrite)
|
||||
d = self._node.modify(a.modify)
|
||||
d.addCallback(lambda res: self)
|
||||
@ -470,10 +516,10 @@ class DirectoryNode:
|
||||
the operation completes."""
|
||||
assert isinstance(name, unicode)
|
||||
if self.is_readonly():
|
||||
return defer.fail(NotMutableError())
|
||||
return defer.fail(NotWriteableError())
|
||||
d = self._uploader.upload(uploadable)
|
||||
d.addCallback(lambda results: results.uri)
|
||||
d.addCallback(self._nodemaker.create_from_cap)
|
||||
d.addCallback(lambda results:
|
||||
self._create_and_validate_node(results.uri, None, name))
|
||||
d.addCallback(lambda node:
|
||||
self.set_node(name, node, metadata, overwrite))
|
||||
return d
|
||||
@ -483,7 +529,7 @@ class DirectoryNode:
|
||||
fires (with the node just removed) when the operation finishes."""
|
||||
assert isinstance(name, unicode)
|
||||
if self.is_readonly():
|
||||
return defer.fail(NotMutableError())
|
||||
return defer.fail(NotWriteableError())
|
||||
deleter = Deleter(self, name)
|
||||
d = self._node.modify(deleter.modify)
|
||||
d.addCallback(lambda res: deleter.old_child)
|
||||
@ -493,7 +539,7 @@ class DirectoryNode:
|
||||
mutable=True):
|
||||
assert isinstance(name, unicode)
|
||||
if self.is_readonly():
|
||||
return defer.fail(NotMutableError())
|
||||
return defer.fail(NotWriteableError())
|
||||
if mutable:
|
||||
d = self._nodemaker.create_new_mutable_directory(initial_children)
|
||||
else:
|
||||
@ -515,7 +561,7 @@ class DirectoryNode:
|
||||
Deferred that fires when the operation finishes."""
|
||||
assert isinstance(current_child_name, unicode)
|
||||
if self.is_readonly() or new_parent.is_readonly():
|
||||
return defer.fail(NotMutableError())
|
||||
return defer.fail(NotWriteableError())
|
||||
if new_child_name is None:
|
||||
new_child_name = current_child_name
|
||||
assert isinstance(new_child_name, unicode)
|
||||
|
@ -17,6 +17,9 @@ from allmydata.immutable import download
|
||||
class _ImmutableFileNodeBase(object):
|
||||
implements(IImmutableFileNode, ICheckable)
|
||||
|
||||
def get_write_uri(self):
|
||||
return None
|
||||
|
||||
def get_readonly_uri(self):
|
||||
return self.get_uri()
|
||||
|
||||
@ -26,6 +29,15 @@ class _ImmutableFileNodeBase(object):
|
||||
def is_readonly(self):
|
||||
return True
|
||||
|
||||
def is_unknown(self):
|
||||
return False
|
||||
|
||||
def is_allowed_in_immutable_directory(self):
|
||||
return True
|
||||
|
||||
def raise_error(self):
|
||||
pass
|
||||
|
||||
def __hash__(self):
|
||||
return self.u.__hash__()
|
||||
def __eq__(self, other):
|
||||
|
@ -456,7 +456,6 @@ class IVerifierURI(Interface, IURI):
|
||||
class IDirnodeURI(Interface):
|
||||
"""I am a URI which represents a dirnode."""
|
||||
|
||||
|
||||
class IFileURI(Interface):
|
||||
"""I am a URI which represents a filenode."""
|
||||
def get_size():
|
||||
@ -467,21 +466,28 @@ class IImmutableFileURI(IFileURI):
|
||||
|
||||
class IMutableFileURI(Interface):
|
||||
"""I am a URI which represents a mutable filenode."""
|
||||
|
||||
class IDirectoryURI(Interface):
|
||||
pass
|
||||
|
||||
class IReadonlyDirectoryURI(Interface):
|
||||
pass
|
||||
|
||||
class CannotPackUnknownNodeError(Exception):
|
||||
"""UnknownNodes (using filecaps from the future that we don't understand)
|
||||
cannot yet be copied safely, so I refuse to copy them."""
|
||||
class CapConstraintError(Exception):
|
||||
"""A constraint on a cap was violated."""
|
||||
|
||||
class UnhandledCapTypeError(Exception):
|
||||
"""I recognize the cap/URI, but I cannot create an IFilesystemNode for
|
||||
it."""
|
||||
class MustBeDeepImmutableError(CapConstraintError):
|
||||
"""Mutable children cannot be added to an immutable directory.
|
||||
Also, caps obtained from an immutable directory can trigger this error
|
||||
if they are later found to refer to a mutable object and then used."""
|
||||
|
||||
class NotDeepImmutableError(Exception):
|
||||
"""Deep-immutable directories can only contain deep-immutable children"""
|
||||
class MustBeReadonlyError(CapConstraintError):
|
||||
"""Known write caps cannot be specified in a ro_uri field. Also,
|
||||
caps obtained from a ro_uri field can trigger this error if they
|
||||
are later found to be write caps and then used."""
|
||||
|
||||
class MustNotBeUnknownRWError(CapConstraintError):
|
||||
"""Cannot add an unknown child cap specified in a rw_uri field."""
|
||||
|
||||
# The hierarchy looks like this:
|
||||
# IFilesystemNode
|
||||
@ -518,9 +524,8 @@ class IFilesystemNode(Interface):
|
||||
"""
|
||||
|
||||
def get_uri():
|
||||
"""
|
||||
Return the URI string that can be used by others to get access to
|
||||
this node. If this node is read-only, the URI will only offer
|
||||
"""Return the URI string corresponding to the strongest cap associated
|
||||
with this node. If this node is read-only, the URI will only offer
|
||||
read-only access. If this node is read-write, the URI will offer
|
||||
read-write access.
|
||||
|
||||
@ -528,6 +533,11 @@ class IFilesystemNode(Interface):
|
||||
read-only access with others, use get_readonly_uri().
|
||||
"""
|
||||
|
||||
def get_write_uri(n):
|
||||
"""Return the URI string that can be used by others to get write
|
||||
access to this node, if it is writeable. If this is a read-only node,
|
||||
return None."""
|
||||
|
||||
def get_readonly_uri():
|
||||
"""Return the URI string that can be used by others to get read-only
|
||||
access to this node. The result is a read-only URI, regardless of
|
||||
@ -557,6 +567,18 @@ class IFilesystemNode(Interface):
|
||||
file.
|
||||
"""
|
||||
|
||||
def is_unknown():
|
||||
"""Return True if this is an unknown node."""
|
||||
|
||||
def is_allowed_in_immutable_directory():
|
||||
"""Return True if this node is allowed as a child of a deep-immutable
|
||||
directory. This is true if either the node is of a known-immutable type,
|
||||
or it is unknown and read-only.
|
||||
"""
|
||||
|
||||
def raise_error():
|
||||
"""Raise any error associated with this node."""
|
||||
|
||||
def get_size():
|
||||
"""Return the length (in bytes) of the data this node represents. For
|
||||
directory nodes, I return the size of the backing store. I return
|
||||
@ -902,7 +924,7 @@ class IDirectoryNode(IFilesystemNode):
|
||||
ctime/mtime semantics of traditional filesystems.
|
||||
|
||||
If this directory node is read-only, the Deferred will errback with a
|
||||
NotMutableError."""
|
||||
NotWriteableError."""
|
||||
|
||||
def set_children(entries, overwrite=True):
|
||||
"""Add multiple children (by writecap+readcap) to a directory node.
|
||||
@ -928,7 +950,7 @@ class IDirectoryNode(IFilesystemNode):
|
||||
ctime/mtime semantics of traditional filesystems.
|
||||
|
||||
If this directory node is read-only, the Deferred will errback with a
|
||||
NotMutableError."""
|
||||
NotWriteableError."""
|
||||
|
||||
def set_nodes(entries, overwrite=True):
|
||||
"""Add multiple children to a directory node. Takes a dict mapping
|
||||
@ -2074,7 +2096,7 @@ class INodeMaker(Interface):
|
||||
Tahoe process will typically have a single NodeMaker, but unit tests may
|
||||
create simplified/mocked forms for testing purposes.
|
||||
"""
|
||||
def create_from_cap(writecap, readcap=None):
|
||||
def create_from_cap(writecap, readcap=None, **kwargs):
|
||||
"""I create an IFilesystemNode from the given writecap/readcap. I can
|
||||
only provide nodes for existing file/directory objects: use my other
|
||||
methods to create new objects. I return synchronously."""
|
||||
|
@ -8,7 +8,7 @@ MODE_WRITE = "MODE_WRITE" # replace all shares, probably.. not for initial
|
||||
# creation
|
||||
MODE_READ = "MODE_READ"
|
||||
|
||||
class NotMutableError(Exception):
|
||||
class NotWriteableError(Exception):
|
||||
pass
|
||||
|
||||
class NeedMoreDataError(Exception):
|
||||
|
@ -214,6 +214,12 @@ class MutableFileNode:
|
||||
|
||||
def get_uri(self):
|
||||
return self._uri.to_string()
|
||||
|
||||
def get_write_uri(self):
|
||||
if self.is_readonly():
|
||||
return None
|
||||
return self._uri.to_string()
|
||||
|
||||
def get_readonly_uri(self):
|
||||
return self._uri.get_readonly().to_string()
|
||||
|
||||
@ -227,9 +233,19 @@ class MutableFileNode:
|
||||
|
||||
def is_mutable(self):
|
||||
return self._uri.is_mutable()
|
||||
|
||||
def is_readonly(self):
|
||||
return self._uri.is_readonly()
|
||||
|
||||
def is_unknown(self):
|
||||
return False
|
||||
|
||||
def is_allowed_in_immutable_directory(self):
|
||||
return not self._uri.is_mutable()
|
||||
|
||||
def raise_error(self):
|
||||
pass
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.__class__, self._uri))
|
||||
def __cmp__(self, them):
|
||||
|
@ -1,7 +1,7 @@
|
||||
import weakref
|
||||
from zope.interface import implements
|
||||
from allmydata.util.assertutil import precondition
|
||||
from allmydata.interfaces import INodeMaker, NotDeepImmutableError
|
||||
from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError
|
||||
from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
|
||||
from allmydata.immutable.upload import Data
|
||||
from allmydata.mutable.filenode import MutableFileNode
|
||||
@ -44,28 +44,33 @@ class NodeMaker:
|
||||
def _create_dirnode(self, filenode):
|
||||
return DirectoryNode(filenode, self, self.uploader)
|
||||
|
||||
def create_from_cap(self, writecap, readcap=None):
|
||||
def create_from_cap(self, writecap, readcap=None, deep_immutable=False, name=u"<unknown name>"):
|
||||
# this returns synchronously. It starts with a "cap string".
|
||||
assert isinstance(writecap, (str, type(None))), type(writecap)
|
||||
assert isinstance(readcap, (str, type(None))), type(readcap)
|
||||
|
||||
bigcap = writecap or readcap
|
||||
if not bigcap:
|
||||
# maybe the writecap was hidden because we're in a readonly
|
||||
# directory, and the future cap format doesn't have a readcap, or
|
||||
# something.
|
||||
return UnknownNode(writecap, readcap)
|
||||
if bigcap in self._node_cache:
|
||||
return self._node_cache[bigcap]
|
||||
cap = uri.from_string(bigcap)
|
||||
node = self._create_from_cap(cap)
|
||||
return UnknownNode(None, None) # deep_immutable and name not needed
|
||||
|
||||
# The name doesn't matter for caching since it's only used in the error
|
||||
# attribute of an UnknownNode, and we don't cache those.
|
||||
memokey = ("I" if deep_immutable else "M") + bigcap
|
||||
if memokey in self._node_cache:
|
||||
return self._node_cache[memokey]
|
||||
cap = uri.from_string(bigcap, deep_immutable=deep_immutable, name=name)
|
||||
node = self._create_from_single_cap(cap)
|
||||
if node:
|
||||
self._node_cache[bigcap] = node # note: WeakValueDictionary
|
||||
self._node_cache[memokey] = node # note: WeakValueDictionary
|
||||
else:
|
||||
node = UnknownNode(writecap, readcap) # don't cache UnknownNode
|
||||
# don't cache UnknownNode
|
||||
node = UnknownNode(writecap, readcap, deep_immutable=deep_immutable, name=name)
|
||||
return node
|
||||
|
||||
def _create_from_cap(self, cap):
|
||||
# This starts with a "cap instance"
|
||||
def _create_from_single_cap(self, cap):
|
||||
if isinstance(cap, uri.LiteralFileURI):
|
||||
return self._create_lit(cap)
|
||||
if isinstance(cap, uri.CHKFileURI):
|
||||
@ -76,7 +81,7 @@ class NodeMaker:
|
||||
uri.ReadonlyDirectoryURI,
|
||||
uri.ImmutableDirectoryURI,
|
||||
uri.LiteralDirectoryURI)):
|
||||
filenode = self._create_from_cap(cap.get_filenode_cap())
|
||||
filenode = self._create_from_single_cap(cap.get_filenode_cap())
|
||||
return self._create_dirnode(filenode)
|
||||
return None
|
||||
|
||||
@ -89,13 +94,11 @@ class NodeMaker:
|
||||
return d
|
||||
|
||||
def create_new_mutable_directory(self, initial_children={}):
|
||||
# initial_children must have metadata (i.e. {} instead of None), and
|
||||
# should not contain UnknownNodes
|
||||
# initial_children must have metadata (i.e. {} instead of None)
|
||||
for (name, (node, metadata)) in initial_children.iteritems():
|
||||
precondition(not isinstance(node, UnknownNode),
|
||||
"create_new_mutable_directory does not accept UnknownNode", node)
|
||||
precondition(isinstance(metadata, dict),
|
||||
"create_new_mutable_directory requires metadata to be a dict, not None", metadata)
|
||||
node.raise_error()
|
||||
d = self.create_mutable_file(lambda n:
|
||||
pack_children(n, initial_children))
|
||||
d.addCallback(self._create_dirnode)
|
||||
@ -105,19 +108,15 @@ class NodeMaker:
|
||||
if convergence is None:
|
||||
convergence = self.secret_holder.get_convergence_secret()
|
||||
for (name, (node, metadata)) in children.iteritems():
|
||||
precondition(not isinstance(node, UnknownNode),
|
||||
"create_immutable_directory does not accept UnknownNode", node)
|
||||
precondition(isinstance(metadata, dict),
|
||||
"create_immutable_directory requires metadata to be a dict, not None", metadata)
|
||||
if node.is_mutable():
|
||||
raise NotDeepImmutableError("%s is not immutable" % (node,))
|
||||
node.raise_error()
|
||||
if not node.is_allowed_in_immutable_directory():
|
||||
raise MustBeDeepImmutableError("%s is not immutable" % (node,), name)
|
||||
n = DummyImmutableFileNode() # writekey=None
|
||||
packed = pack_children(n, children)
|
||||
uploadable = Data(packed, convergence)
|
||||
d = self.uploader.upload(uploadable, history=self.history)
|
||||
def _uploaded(results):
|
||||
filecap = self.create_from_cap(results.uri)
|
||||
return filecap
|
||||
d.addCallback(_uploaded)
|
||||
d.addCallback(lambda results: self.create_from_cap(None, results.uri))
|
||||
d.addCallback(self._create_dirnode)
|
||||
return d
|
||||
|
@ -128,12 +128,14 @@ class UnknownAliasError(Exception):
|
||||
pass
|
||||
|
||||
def get_alias(aliases, path, default):
|
||||
from allmydata import uri
|
||||
# transform "work:path/filename" into (aliases["work"], "path/filename").
|
||||
# If default=None, then an empty alias is indicated by returning
|
||||
# DefaultAliasMarker. We special-case "URI:" to make it easy to access
|
||||
# specific files/directories by their read-cap.
|
||||
# DefaultAliasMarker. We special-case strings with a recognized cap URI
|
||||
# prefix, to make it easy to access specific files/directories by their
|
||||
# caps.
|
||||
path = path.strip()
|
||||
if path.startswith("URI:"):
|
||||
if uri.has_uri_prefix(path):
|
||||
# The only way to get a sub-path is to use URI:blah:./foo, and we
|
||||
# strip out the :./ sequence.
|
||||
sep = path.find(":./")
|
||||
|
@ -258,8 +258,7 @@ class TahoeDirectorySource:
|
||||
readcap = ascii_or_none(data[1].get("ro_uri"))
|
||||
self.children[name] = TahoeFileSource(self.nodeurl, mutable,
|
||||
writecap, readcap)
|
||||
else:
|
||||
assert data[0] == "dirnode"
|
||||
elif data[0] == "dirnode":
|
||||
writecap = ascii_or_none(data[1].get("rw_uri"))
|
||||
readcap = ascii_or_none(data[1].get("ro_uri"))
|
||||
if writecap and writecap in self.cache:
|
||||
@ -277,6 +276,11 @@ class TahoeDirectorySource:
|
||||
if recurse:
|
||||
child.populate(True)
|
||||
self.children[name] = child
|
||||
else:
|
||||
# TODO: there should be an option to skip unknown nodes.
|
||||
raise TahoeError("Cannot copy unknown nodes (ticket #839). "
|
||||
"You probably need to use a later version of "
|
||||
"Tahoe-LAFS to copy this directory.")
|
||||
|
||||
class TahoeMissingTarget:
|
||||
def __init__(self, url):
|
||||
@ -353,8 +357,7 @@ class TahoeDirectoryTarget:
|
||||
urllib.quote(name.encode('utf-8'))])
|
||||
self.children[name] = TahoeFileTarget(self.nodeurl, mutable,
|
||||
writecap, readcap, url)
|
||||
else:
|
||||
assert data[0] == "dirnode"
|
||||
elif data[0] == "dirnode":
|
||||
writecap = ascii_or_none(data[1].get("rw_uri"))
|
||||
readcap = ascii_or_none(data[1].get("ro_uri"))
|
||||
if writecap and writecap in self.cache:
|
||||
@ -372,6 +375,11 @@ class TahoeDirectoryTarget:
|
||||
if recurse:
|
||||
child.populate(True)
|
||||
self.children[name] = child
|
||||
else:
|
||||
# TODO: there should be an option to skip unknown nodes.
|
||||
raise TahoeError("Cannot copy unknown nodes (ticket #839). "
|
||||
"You probably need to use a later version of "
|
||||
"Tahoe-LAFS to copy this directory.")
|
||||
|
||||
def get_child_target(self, name):
|
||||
# return a new target for a named subdirectory of this dir
|
||||
@ -407,9 +415,11 @@ class TahoeDirectoryTarget:
|
||||
set_data = {}
|
||||
for (name, filecap) in self.new_children.items():
|
||||
# it just so happens that ?t=set_children will accept both file
|
||||
# read-caps and write-caps as ['rw_uri'], and will handle eithe
|
||||
# read-caps and write-caps as ['rw_uri'], and will handle either
|
||||
# correctly. So don't bother trying to figure out whether the one
|
||||
# we have is read-only or read-write.
|
||||
# TODO: think about how this affects forward-compatibility for
|
||||
# unknown caps
|
||||
set_data[name] = ["filenode", {"rw_uri": filecap}]
|
||||
body = simplejson.dumps(set_data)
|
||||
POST(url, body)
|
||||
@ -770,6 +780,7 @@ def copy(options):
|
||||
# local-file-in-the-way
|
||||
# touch proposed
|
||||
# tahoe cp -r my:docs/proposed/denver.txt proposed/denver.txt
|
||||
# handling of unknown nodes
|
||||
|
||||
# things that maybe should be errors but aren't
|
||||
# local-dir-in-the-way
|
||||
|
@ -40,6 +40,7 @@ def put(options):
|
||||
# DIRCAP:./subdir/foo : DIRCAP/subdir/foo
|
||||
# MUTABLE-FILE-WRITECAP : filecap
|
||||
|
||||
# FIXME: this shouldn't rely on a particular prefix.
|
||||
if to_file.startswith("URI:SSK:"):
|
||||
url = nodeurl + "uri/%s" % urllib.quote(to_file)
|
||||
else:
|
||||
|
@ -51,6 +51,8 @@ class FakeCHKFileNode:
|
||||
|
||||
def get_uri(self):
|
||||
return self.my_uri.to_string()
|
||||
def get_write_uri(self):
|
||||
return None
|
||||
def get_readonly_uri(self):
|
||||
return self.my_uri.to_string()
|
||||
def get_cap(self):
|
||||
@ -103,6 +105,12 @@ class FakeCHKFileNode:
|
||||
return False
|
||||
def is_readonly(self):
|
||||
return True
|
||||
def is_unknown(self):
|
||||
return False
|
||||
def is_allowed_in_immutable_directory(self):
|
||||
return True
|
||||
def raise_error(self):
|
||||
pass
|
||||
|
||||
def get_size(self):
|
||||
try:
|
||||
@ -190,6 +198,10 @@ class FakeMutableFileNode:
|
||||
return self.my_uri.get_readonly()
|
||||
def get_uri(self):
|
||||
return self.my_uri.to_string()
|
||||
def get_write_uri(self):
|
||||
if self.is_readonly():
|
||||
return None
|
||||
return self.my_uri.to_string()
|
||||
def get_readonly(self):
|
||||
return self.my_uri.get_readonly()
|
||||
def get_readonly_uri(self):
|
||||
@ -200,6 +212,12 @@ class FakeMutableFileNode:
|
||||
return self.my_uri.is_readonly()
|
||||
def is_mutable(self):
|
||||
return self.my_uri.is_mutable()
|
||||
def is_unknown(self):
|
||||
return False
|
||||
def is_allowed_in_immutable_directory(self):
|
||||
return not self.my_uri.is_mutable()
|
||||
def raise_error(self):
|
||||
pass
|
||||
def get_writekey(self):
|
||||
return "\x00"*16
|
||||
def get_size(self):
|
||||
|
@ -288,11 +288,15 @@ class NodeMaker(unittest.TestCase):
|
||||
self.failUnless(n.is_readonly())
|
||||
self.failUnless(n.is_mutable())
|
||||
|
||||
future = "x-tahoe-crazy://future_cap_format."
|
||||
n = c.create_node_from_uri(future)
|
||||
unknown_rw = "lafs://from_the_future"
|
||||
unknown_ro = "lafs://readonly_from_the_future"
|
||||
n = c.create_node_from_uri(unknown_rw, unknown_ro)
|
||||
self.failUnless(IFilesystemNode.providedBy(n))
|
||||
self.failIf(IFileNode.providedBy(n))
|
||||
self.failIf(IImmutableFileNode.providedBy(n))
|
||||
self.failIf(IMutableFileNode.providedBy(n))
|
||||
self.failIf(IDirectoryNode.providedBy(n))
|
||||
self.failUnlessEqual(n.get_uri(), future)
|
||||
self.failUnless(n.is_unknown())
|
||||
self.failUnlessEqual(n.get_uri(), unknown_rw)
|
||||
self.failUnlessEqual(n.get_write_uri(), unknown_rw)
|
||||
self.failUnlessEqual(n.get_readonly_uri(), "ro." + unknown_ro)
|
||||
|
@ -7,8 +7,9 @@ from allmydata import uri, dirnode
|
||||
from allmydata.client import Client
|
||||
from allmydata.immutable import upload
|
||||
from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \
|
||||
ExistingChildError, NoSuchChildError, NotDeepImmutableError, \
|
||||
IDeepCheckResults, IDeepCheckAndRepairResults, CannotPackUnknownNodeError
|
||||
ExistingChildError, NoSuchChildError, MustNotBeUnknownRWError, \
|
||||
MustBeDeepImmutableError, MustBeReadonlyError, \
|
||||
IDeepCheckResults, IDeepCheckAndRepairResults
|
||||
from allmydata.mutable.filenode import MutableFileNode
|
||||
from allmydata.mutable.common import UncoordinatedWriteError
|
||||
from allmydata.util import hashutil, base32
|
||||
@ -16,7 +17,7 @@ from allmydata.monitor import Monitor
|
||||
from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \
|
||||
ErrorMixin
|
||||
from allmydata.test.no_network import GridTestMixin
|
||||
from allmydata.unknown import UnknownNode
|
||||
from allmydata.unknown import UnknownNode, strip_prefix_for_ro
|
||||
from allmydata.nodemaker import NodeMaker
|
||||
from base64 import b32decode
|
||||
import common_util as testutil
|
||||
@ -32,6 +33,11 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
d = c.create_dirnode()
|
||||
def _done(res):
|
||||
self.failUnless(isinstance(res, dirnode.DirectoryNode))
|
||||
self.failUnless(res.is_mutable())
|
||||
self.failIf(res.is_readonly())
|
||||
self.failIf(res.is_unknown())
|
||||
self.failIf(res.is_allowed_in_immutable_directory())
|
||||
res.raise_error()
|
||||
rep = str(res)
|
||||
self.failUnless("RW-MUT" in rep)
|
||||
d.addCallback(_done)
|
||||
@ -44,36 +50,74 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
nm = c.nodemaker
|
||||
setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
|
||||
one_uri = "URI:LIT:n5xgk" # LIT for "one"
|
||||
mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
|
||||
mut_read_uri = "URI:SSK-RO:jf6wkflosyvntwxqcdo7a54jvm:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
|
||||
future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
kids = {u"one": (nm.create_from_cap(one_uri), {}),
|
||||
u"two": (nm.create_from_cap(setup_py_uri),
|
||||
{"metakey": "metavalue"}),
|
||||
u"mut": (nm.create_from_cap(mut_write_uri, mut_read_uri), {}),
|
||||
u"fut": (nm.create_from_cap(future_write_uri, future_read_uri), {}),
|
||||
u"fro": (nm.create_from_cap(None, future_read_uri), {}),
|
||||
}
|
||||
d = c.create_dirnode(kids)
|
||||
|
||||
def _created(dn):
|
||||
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
|
||||
self.failUnless(dn.is_mutable())
|
||||
self.failIf(dn.is_readonly())
|
||||
self.failIf(dn.is_unknown())
|
||||
self.failIf(dn.is_allowed_in_immutable_directory())
|
||||
dn.raise_error()
|
||||
rep = str(dn)
|
||||
self.failUnless("RW-MUT" in rep)
|
||||
return dn.list()
|
||||
d.addCallback(_created)
|
||||
|
||||
def _check_kids(children):
|
||||
self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"])
|
||||
self.failUnlessEqual(sorted(children.keys()),
|
||||
[u"fro", u"fut", u"mut", u"one", u"two"])
|
||||
one_node, one_metadata = children[u"one"]
|
||||
two_node, two_metadata = children[u"two"]
|
||||
mut_node, mut_metadata = children[u"mut"]
|
||||
fut_node, fut_metadata = children[u"fut"]
|
||||
fro_node, fro_metadata = children[u"fro"]
|
||||
|
||||
self.failUnlessEqual(one_node.get_size(), 3)
|
||||
self.failUnlessEqual(two_node.get_size(), 14861)
|
||||
self.failUnlessEqual(one_node.get_uri(), one_uri)
|
||||
self.failUnlessEqual(one_node.get_readonly_uri(), one_uri)
|
||||
self.failUnless(isinstance(one_metadata, dict), one_metadata)
|
||||
|
||||
self.failUnlessEqual(two_node.get_size(), 14861)
|
||||
self.failUnlessEqual(two_node.get_uri(), setup_py_uri)
|
||||
self.failUnlessEqual(two_node.get_readonly_uri(), setup_py_uri)
|
||||
self.failUnlessEqual(two_metadata["metakey"], "metavalue")
|
||||
|
||||
self.failUnlessEqual(mut_node.get_uri(), mut_write_uri)
|
||||
self.failUnlessEqual(mut_node.get_readonly_uri(), mut_read_uri)
|
||||
self.failUnless(isinstance(mut_metadata, dict), mut_metadata)
|
||||
|
||||
self.failUnless(fut_node.is_unknown())
|
||||
self.failUnlessEqual(fut_node.get_uri(), future_write_uri)
|
||||
self.failUnlessEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri)
|
||||
self.failUnless(isinstance(fut_metadata, dict), fut_metadata)
|
||||
|
||||
self.failUnless(fro_node.is_unknown())
|
||||
self.failUnlessEqual(fro_node.get_uri(), "ro." + future_read_uri)
|
||||
self.failUnlessEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri)
|
||||
self.failUnless(isinstance(fro_metadata, dict), fro_metadata)
|
||||
d.addCallback(_check_kids)
|
||||
|
||||
d.addCallback(lambda ign: nm.create_new_mutable_directory(kids))
|
||||
d.addCallback(lambda dn: dn.list())
|
||||
d.addCallback(_check_kids)
|
||||
future_writecap = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
future_node = UnknownNode(future_writecap, future_readcap)
|
||||
bad_kids1 = {u"one": (future_node, {})}
|
||||
|
||||
bad_future_node = UnknownNode(future_write_uri, None)
|
||||
bad_kids1 = {u"one": (bad_future_node, {})}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(AssertionError, "bad_kids1",
|
||||
"does not accept UnknownNode",
|
||||
self.shouldFail(MustNotBeUnknownRWError, "bad_kids1",
|
||||
"cannot attach unknown",
|
||||
nm.create_new_mutable_directory,
|
||||
bad_kids1))
|
||||
bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)}
|
||||
@ -91,17 +135,24 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
nm = c.nodemaker
|
||||
setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
|
||||
one_uri = "URI:LIT:n5xgk" # LIT for "one"
|
||||
mut_readcap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
|
||||
mut_writecap = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
|
||||
mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
|
||||
mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
|
||||
future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
kids = {u"one": (nm.create_from_cap(one_uri), {}),
|
||||
u"two": (nm.create_from_cap(setup_py_uri),
|
||||
{"metakey": "metavalue"}),
|
||||
u"fut": (nm.create_from_cap(None, future_read_uri), {}),
|
||||
}
|
||||
d = c.create_immutable_dirnode(kids)
|
||||
|
||||
def _created(dn):
|
||||
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
|
||||
self.failIf(dn.is_mutable())
|
||||
self.failUnless(dn.is_readonly())
|
||||
self.failIf(dn.is_unknown())
|
||||
self.failUnless(dn.is_allowed_in_immutable_directory())
|
||||
dn.raise_error()
|
||||
rep = str(dn)
|
||||
self.failUnless("RO-IMM" in rep)
|
||||
cap = dn.get_cap()
|
||||
@ -109,50 +160,73 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
self.cap = cap
|
||||
return dn.list()
|
||||
d.addCallback(_created)
|
||||
|
||||
def _check_kids(children):
|
||||
self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"])
|
||||
self.failUnlessEqual(sorted(children.keys()), [u"fut", u"one", u"two"])
|
||||
one_node, one_metadata = children[u"one"]
|
||||
two_node, two_metadata = children[u"two"]
|
||||
fut_node, fut_metadata = children[u"fut"]
|
||||
|
||||
self.failUnlessEqual(one_node.get_size(), 3)
|
||||
self.failUnlessEqual(two_node.get_size(), 14861)
|
||||
self.failUnlessEqual(one_node.get_uri(), one_uri)
|
||||
self.failUnlessEqual(one_node.get_readonly_uri(), one_uri)
|
||||
self.failUnless(isinstance(one_metadata, dict), one_metadata)
|
||||
|
||||
self.failUnlessEqual(two_node.get_size(), 14861)
|
||||
self.failUnlessEqual(two_node.get_uri(), setup_py_uri)
|
||||
self.failUnlessEqual(two_node.get_readonly_uri(), setup_py_uri)
|
||||
self.failUnlessEqual(two_metadata["metakey"], "metavalue")
|
||||
|
||||
self.failUnless(fut_node.is_unknown())
|
||||
self.failUnlessEqual(fut_node.get_uri(), "imm." + future_read_uri)
|
||||
self.failUnlessEqual(fut_node.get_readonly_uri(), "imm." + future_read_uri)
|
||||
self.failUnless(isinstance(fut_metadata, dict), fut_metadata)
|
||||
d.addCallback(_check_kids)
|
||||
|
||||
d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
|
||||
d.addCallback(lambda dn: dn.list())
|
||||
d.addCallback(_check_kids)
|
||||
future_writecap = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
future_node = UnknownNode(future_writecap, future_readcap)
|
||||
bad_kids1 = {u"one": (future_node, {})}
|
||||
|
||||
bad_future_node1 = UnknownNode(future_write_uri, None)
|
||||
bad_kids1 = {u"one": (bad_future_node1, {})}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(AssertionError, "bad_kids1",
|
||||
"does not accept UnknownNode",
|
||||
self.shouldFail(MustNotBeUnknownRWError, "bad_kids1",
|
||||
"cannot attach unknown",
|
||||
c.create_immutable_dirnode,
|
||||
bad_kids1))
|
||||
bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)}
|
||||
bad_future_node2 = UnknownNode(future_write_uri, future_read_uri)
|
||||
bad_kids2 = {u"one": (bad_future_node2, {})}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(AssertionError, "bad_kids2",
|
||||
"requires metadata to be a dict",
|
||||
c.create_immutable_dirnode,
|
||||
bad_kids2))
|
||||
bad_kids3 = {u"one": (nm.create_from_cap(mut_writecap), {})}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(NotDeepImmutableError, "bad_kids3",
|
||||
self.shouldFail(MustBeDeepImmutableError, "bad_kids2",
|
||||
"is not immutable",
|
||||
c.create_immutable_dirnode,
|
||||
bad_kids3))
|
||||
bad_kids4 = {u"one": (nm.create_from_cap(mut_readcap), {})}
|
||||
bad_kids2))
|
||||
bad_kids3 = {u"one": (nm.create_from_cap(one_uri), None)}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(NotDeepImmutableError, "bad_kids4",
|
||||
self.shouldFail(AssertionError, "bad_kids3",
|
||||
"requires metadata to be a dict",
|
||||
c.create_immutable_dirnode,
|
||||
bad_kids3))
|
||||
bad_kids4 = {u"one": (nm.create_from_cap(mut_write_uri), {})}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(MustBeDeepImmutableError, "bad_kids4",
|
||||
"is not immutable",
|
||||
c.create_immutable_dirnode,
|
||||
bad_kids4))
|
||||
bad_kids5 = {u"one": (nm.create_from_cap(mut_read_uri), {})}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(MustBeDeepImmutableError, "bad_kids5",
|
||||
"is not immutable",
|
||||
c.create_immutable_dirnode,
|
||||
bad_kids5))
|
||||
d.addCallback(lambda ign: c.create_immutable_dirnode({}))
|
||||
def _created_empty(dn):
|
||||
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
|
||||
self.failIf(dn.is_mutable())
|
||||
self.failUnless(dn.is_readonly())
|
||||
self.failIf(dn.is_unknown())
|
||||
self.failUnless(dn.is_allowed_in_immutable_directory())
|
||||
dn.raise_error()
|
||||
rep = str(dn)
|
||||
self.failUnless("RO-IMM" in rep)
|
||||
cap = dn.get_cap()
|
||||
@ -168,6 +242,9 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
|
||||
self.failIf(dn.is_mutable())
|
||||
self.failUnless(dn.is_readonly())
|
||||
self.failIf(dn.is_unknown())
|
||||
self.failUnless(dn.is_allowed_in_immutable_directory())
|
||||
dn.raise_error()
|
||||
rep = str(dn)
|
||||
self.failUnless("RO-IMM" in rep)
|
||||
cap = dn.get_cap()
|
||||
@ -193,9 +270,9 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
d.addCallback(_check_kids)
|
||||
d.addCallback(lambda ign: n.get(u"subdir"))
|
||||
d.addCallback(lambda sd: self.failIf(sd.is_mutable()))
|
||||
bad_kids = {u"one": (nm.create_from_cap(mut_writecap), {})}
|
||||
bad_kids = {u"one": (nm.create_from_cap(mut_write_uri), {})}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(NotDeepImmutableError, "YZ",
|
||||
self.shouldFail(MustBeDeepImmutableError, "YZ",
|
||||
"is not immutable",
|
||||
n.create_subdirectory,
|
||||
u"sub2", bad_kids, mutable=False))
|
||||
@ -203,7 +280,6 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
d.addCallback(_made_parent)
|
||||
return d
|
||||
|
||||
|
||||
def test_check(self):
|
||||
self.basedir = "dirnode/Dirnode/test_check"
|
||||
self.set_up_grid()
|
||||
@ -337,24 +413,27 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
ro_dn = c.create_node_from_uri(ro_uri)
|
||||
self.failUnless(ro_dn.is_readonly())
|
||||
self.failUnless(ro_dn.is_mutable())
|
||||
self.failIf(ro_dn.is_unknown())
|
||||
self.failIf(ro_dn.is_allowed_in_immutable_directory())
|
||||
ro_dn.raise_error()
|
||||
|
||||
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
|
||||
self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
|
||||
ro_dn.set_uri, u"newchild", filecap, filecap)
|
||||
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
|
||||
self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
|
||||
ro_dn.set_node, u"newchild", filenode)
|
||||
self.shouldFail(dirnode.NotMutableError, "set_nodes ro", None,
|
||||
self.shouldFail(dirnode.NotWriteableError, "set_nodes ro", None,
|
||||
ro_dn.set_nodes, { u"newchild": (filenode, None) })
|
||||
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
|
||||
self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
|
||||
ro_dn.add_file, u"newchild", uploadable)
|
||||
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
|
||||
self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
|
||||
ro_dn.delete, u"child")
|
||||
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
|
||||
self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
|
||||
ro_dn.create_subdirectory, u"newchild")
|
||||
self.shouldFail(dirnode.NotMutableError, "set_metadata_for ro", None,
|
||||
self.shouldFail(dirnode.NotWriteableError, "set_metadata_for ro", None,
|
||||
ro_dn.set_metadata_for, u"child", {})
|
||||
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
|
||||
self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
|
||||
ro_dn.move_child_to, u"child", rw_dn)
|
||||
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
|
||||
self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
|
||||
rw_dn.move_child_to, u"child", ro_dn)
|
||||
return ro_dn.list()
|
||||
d.addCallback(_ready)
|
||||
@ -901,8 +980,8 @@ class Packing(unittest.TestCase):
|
||||
nodemaker = NodeMaker(None, None, None,
|
||||
None, None, None,
|
||||
{"k": 3, "n": 10}, None)
|
||||
writecap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
|
||||
filenode = nodemaker.create_from_cap(writecap)
|
||||
write_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
|
||||
filenode = nodemaker.create_from_cap(write_uri)
|
||||
node = dirnode.DirectoryNode(filenode, nodemaker, None)
|
||||
children = node._unpack_contents(known_tree)
|
||||
self._check_children(children)
|
||||
@ -975,23 +1054,23 @@ class Packing(unittest.TestCase):
|
||||
self.failUnlessIn("lit", packed)
|
||||
|
||||
kids = self._make_kids(nm, ["imm", "lit", "write"])
|
||||
self.failUnlessRaises(dirnode.MustBeDeepImmutable,
|
||||
self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
|
||||
dirnode.pack_children,
|
||||
fn, kids, deep_immutable=True)
|
||||
|
||||
# read-only is not enough: all children must be immutable
|
||||
kids = self._make_kids(nm, ["imm", "lit", "read"])
|
||||
self.failUnlessRaises(dirnode.MustBeDeepImmutable,
|
||||
self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
|
||||
dirnode.pack_children,
|
||||
fn, kids, deep_immutable=True)
|
||||
|
||||
kids = self._make_kids(nm, ["imm", "lit", "dirwrite"])
|
||||
self.failUnlessRaises(dirnode.MustBeDeepImmutable,
|
||||
self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
|
||||
dirnode.pack_children,
|
||||
fn, kids, deep_immutable=True)
|
||||
|
||||
kids = self._make_kids(nm, ["imm", "lit", "dirread"])
|
||||
self.failUnlessRaises(dirnode.MustBeDeepImmutable,
|
||||
self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
|
||||
dirnode.pack_children,
|
||||
fn, kids, deep_immutable=True)
|
||||
|
||||
@ -1017,16 +1096,31 @@ class FakeMutableFile:
|
||||
|
||||
def get_cap(self):
|
||||
return self.uri
|
||||
|
||||
def get_uri(self):
|
||||
return self.uri.to_string()
|
||||
|
||||
def get_write_uri(self):
|
||||
return self.uri.to_string()
|
||||
|
||||
def download_best_version(self):
|
||||
return defer.succeed(self.data)
|
||||
|
||||
def get_writekey(self):
|
||||
return "writekey"
|
||||
|
||||
def is_readonly(self):
|
||||
return False
|
||||
|
||||
def is_mutable(self):
|
||||
return True
|
||||
|
||||
def is_unknown(self):
|
||||
return False
|
||||
|
||||
def is_allowed_in_immutable_directory(self):
|
||||
return False
|
||||
|
||||
def modify(self, modifier):
|
||||
self.data = modifier(self.data, None, True)
|
||||
return defer.succeed(None)
|
||||
@ -1049,50 +1143,186 @@ class Dirnode2(unittest.TestCase, testutil.ShouldFailMixin):
|
||||
self.nodemaker = client.nodemaker
|
||||
|
||||
def test_from_future(self):
|
||||
# create a dirnode that contains unknown URI types, and make sure we
|
||||
# tolerate them properly. Since dirnodes aren't allowed to add
|
||||
# unknown node types, we have to be tricky.
|
||||
# Create a mutable directory that contains unknown URI types, and make sure
|
||||
# we tolerate them properly.
|
||||
d = self.nodemaker.create_new_mutable_directory()
|
||||
future_writecap = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
future_node = UnknownNode(future_writecap, future_readcap)
|
||||
future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
future_imm_uri = "x-tahoe-crazy-immutable://I_am_from_the_future."
|
||||
future_node = UnknownNode(future_write_uri, future_read_uri)
|
||||
def _then(n):
|
||||
self._node = n
|
||||
return n.set_node(u"future", future_node)
|
||||
d.addCallback(_then)
|
||||
|
||||
# we should be prohibited from adding an unknown URI to a directory,
|
||||
# since we don't know how to diminish the cap to a readcap (for the
|
||||
# dirnode's rocap slot), and we don't want to accidentally grant
|
||||
# write access to a holder of the dirnode's readcap.
|
||||
# We should be prohibited from adding an unknown URI to a directory
|
||||
# just in the rw_uri slot, since we don't know how to diminish the cap
|
||||
# to a readcap (for the ro_uri slot).
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(CannotPackUnknownNodeError,
|
||||
self.shouldFail(MustNotBeUnknownRWError,
|
||||
"copy unknown",
|
||||
"cannot pack unknown node as child add",
|
||||
"cannot attach unknown rw cap as child",
|
||||
self._node.set_uri, u"add",
|
||||
future_writecap, future_readcap))
|
||||
future_write_uri, None))
|
||||
|
||||
# However, we should be able to add both rw_uri and ro_uri as a pair of
|
||||
# unknown URIs.
|
||||
d.addCallback(lambda ign: self._node.set_uri(u"add-pair",
|
||||
future_write_uri, future_read_uri))
|
||||
|
||||
# and to add an URI prefixed with "ro." or "imm." when it is given in a
|
||||
# write slot (or URL parameter).
|
||||
d.addCallback(lambda ign: self._node.set_uri(u"add-ro",
|
||||
"ro." + future_read_uri, None))
|
||||
d.addCallback(lambda ign: self._node.set_uri(u"add-imm",
|
||||
"imm." + future_imm_uri, None))
|
||||
|
||||
d.addCallback(lambda ign: self._node.list())
|
||||
def _check(children):
|
||||
self.failUnlessEqual(len(children), 1)
|
||||
self.failUnlessEqual(len(children), 4)
|
||||
(fn, metadata) = children[u"future"]
|
||||
self.failUnless(isinstance(fn, UnknownNode), fn)
|
||||
self.failUnlessEqual(fn.get_uri(), future_writecap)
|
||||
self.failUnlessEqual(fn.get_readonly_uri(), future_readcap)
|
||||
# but we *should* be allowed to copy this node, because the
|
||||
# UnknownNode contains all the information that was in the
|
||||
# original directory (readcap and writecap), so we're preserving
|
||||
# everything.
|
||||
self.failUnlessEqual(fn.get_uri(), future_write_uri)
|
||||
self.failUnlessEqual(fn.get_write_uri(), future_write_uri)
|
||||
self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri)
|
||||
|
||||
(fn2, metadata2) = children[u"add-pair"]
|
||||
self.failUnless(isinstance(fn2, UnknownNode), fn2)
|
||||
self.failUnlessEqual(fn2.get_uri(), future_write_uri)
|
||||
self.failUnlessEqual(fn2.get_write_uri(), future_write_uri)
|
||||
self.failUnlessEqual(fn2.get_readonly_uri(), "ro." + future_read_uri)
|
||||
|
||||
(fn3, metadata3) = children[u"add-ro"]
|
||||
self.failUnless(isinstance(fn3, UnknownNode), fn3)
|
||||
self.failUnlessEqual(fn3.get_uri(), "ro." + future_read_uri)
|
||||
self.failUnlessEqual(fn3.get_write_uri(), None)
|
||||
self.failUnlessEqual(fn3.get_readonly_uri(), "ro." + future_read_uri)
|
||||
|
||||
(fn4, metadata4) = children[u"add-imm"]
|
||||
self.failUnless(isinstance(fn4, UnknownNode), fn4)
|
||||
self.failUnlessEqual(fn4.get_uri(), "imm." + future_imm_uri)
|
||||
self.failUnlessEqual(fn4.get_write_uri(), None)
|
||||
self.failUnlessEqual(fn4.get_readonly_uri(), "imm." + future_imm_uri)
|
||||
|
||||
# We should also be allowed to copy the "future" UnknownNode, because
|
||||
# it contains all the information that was in the original directory
|
||||
# (readcap and writecap), so we're preserving everything.
|
||||
return self._node.set_node(u"copy", fn)
|
||||
d.addCallback(_check)
|
||||
|
||||
d.addCallback(lambda ign: self._node.list())
|
||||
def _check2(children):
|
||||
self.failUnlessEqual(len(children), 2)
|
||||
self.failUnlessEqual(len(children), 5)
|
||||
(fn, metadata) = children[u"copy"]
|
||||
self.failUnless(isinstance(fn, UnknownNode), fn)
|
||||
self.failUnlessEqual(fn.get_uri(), future_writecap)
|
||||
self.failUnlessEqual(fn.get_readonly_uri(), future_readcap)
|
||||
self.failUnlessEqual(fn.get_uri(), future_write_uri)
|
||||
self.failUnlessEqual(fn.get_write_uri(), future_write_uri)
|
||||
self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri)
|
||||
d.addCallback(_check2)
|
||||
return d
|
||||
|
||||
def test_unknown_strip_prefix_for_ro(self):
|
||||
self.failUnlessEqual(strip_prefix_for_ro("foo", False), "foo")
|
||||
self.failUnlessEqual(strip_prefix_for_ro("ro.foo", False), "foo")
|
||||
self.failUnlessEqual(strip_prefix_for_ro("imm.foo", False), "imm.foo")
|
||||
self.failUnlessEqual(strip_prefix_for_ro("foo", True), "foo")
|
||||
self.failUnlessEqual(strip_prefix_for_ro("ro.foo", True), "foo")
|
||||
self.failUnlessEqual(strip_prefix_for_ro("imm.foo", True), "foo")
|
||||
|
||||
def test_unknownnode(self):
|
||||
mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
|
||||
mut_read_uri = "URI:SSK-RO:jf6wkflosyvntwxqcdo7a54jvm:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
|
||||
lit_uri = "URI:LIT:n5xgk"
|
||||
|
||||
# This does not attempt to be exhaustive.
|
||||
no_no = [# Opaque node, but not an error.
|
||||
( 0, UnknownNode(None, None)),
|
||||
( 1, UnknownNode(None, None, deep_immutable=True)),
|
||||
]
|
||||
unknown_rw = [# These are errors because we're only given a rw_uri, and we can't
|
||||
# diminish it.
|
||||
( 2, UnknownNode("foo", None)),
|
||||
( 3, UnknownNode("foo", None, deep_immutable=True)),
|
||||
( 4, UnknownNode("ro.foo", None, deep_immutable=True)),
|
||||
( 5, UnknownNode("ro." + mut_read_uri, None, deep_immutable=True)),
|
||||
( 6, UnknownNode("URI:SSK-RO:foo", None, deep_immutable=True)),
|
||||
( 7, UnknownNode("URI:SSK:foo", None)),
|
||||
]
|
||||
must_be_ro = [# These are errors because a readonly constraint is not met.
|
||||
( 8, UnknownNode("ro." + mut_write_uri, None)),
|
||||
( 9, UnknownNode(None, "ro." + mut_write_uri)),
|
||||
]
|
||||
must_be_imm = [# These are errors because an immutable constraint is not met.
|
||||
(10, UnknownNode(None, "ro.URI:SSK-RO:foo", deep_immutable=True)),
|
||||
(11, UnknownNode(None, "imm.URI:SSK:foo")),
|
||||
(12, UnknownNode(None, "imm.URI:SSK-RO:foo")),
|
||||
(13, UnknownNode("bar", "ro.foo", deep_immutable=True)),
|
||||
(14, UnknownNode("bar", "imm.foo", deep_immutable=True)),
|
||||
(15, UnknownNode("bar", "imm." + lit_uri, deep_immutable=True)),
|
||||
(16, UnknownNode("imm." + mut_write_uri, None)),
|
||||
(17, UnknownNode("imm." + mut_read_uri, None)),
|
||||
(18, UnknownNode("bar", "imm.foo")),
|
||||
]
|
||||
bad_uri = [# These are errors because the URI is bad once we've stripped the prefix.
|
||||
(19, UnknownNode("ro.URI:SSK-RO:foo", None)),
|
||||
(20, UnknownNode("imm.URI:CHK:foo", None, deep_immutable=True)),
|
||||
]
|
||||
ro_prefixed = [# These are valid, and the readcap should end up with a ro. prefix.
|
||||
(21, UnknownNode(None, "foo")),
|
||||
(22, UnknownNode(None, "ro.foo")),
|
||||
(32, UnknownNode(None, "ro." + lit_uri)),
|
||||
(23, UnknownNode("bar", "foo")),
|
||||
(24, UnknownNode("bar", "ro.foo")),
|
||||
(32, UnknownNode("bar", "ro." + lit_uri)),
|
||||
(25, UnknownNode("ro.foo", None)),
|
||||
(30, UnknownNode("ro." + lit_uri, None)),
|
||||
]
|
||||
imm_prefixed = [# These are valid, and the readcap should end up with an imm. prefix.
|
||||
(26, UnknownNode(None, "foo", deep_immutable=True)),
|
||||
(27, UnknownNode(None, "ro.foo", deep_immutable=True)),
|
||||
(28, UnknownNode(None, "imm.foo")),
|
||||
(29, UnknownNode(None, "imm.foo", deep_immutable=True)),
|
||||
(31, UnknownNode("imm." + lit_uri, None)),
|
||||
(31, UnknownNode("imm." + lit_uri, None, deep_immutable=True)),
|
||||
(33, UnknownNode(None, "imm." + lit_uri)),
|
||||
(33, UnknownNode(None, "imm." + lit_uri, deep_immutable=True)),
|
||||
]
|
||||
error = unknown_rw + must_be_ro + must_be_imm + bad_uri
|
||||
ok = ro_prefixed + imm_prefixed
|
||||
|
||||
for (i, n) in no_no + error + ok:
|
||||
self.failUnless(n.is_unknown(), i)
|
||||
|
||||
for (i, n) in no_no + error:
|
||||
self.failUnless(n.get_uri() is None, i)
|
||||
self.failUnless(n.get_write_uri() is None, i)
|
||||
self.failUnless(n.get_readonly_uri() is None, i)
|
||||
|
||||
for (i, n) in no_no + ok:
|
||||
n.raise_error()
|
||||
|
||||
for (i, n) in unknown_rw:
|
||||
self.failUnlessRaises(MustNotBeUnknownRWError, lambda: n.raise_error())
|
||||
|
||||
for (i, n) in must_be_ro:
|
||||
self.failUnlessRaises(MustBeReadonlyError, lambda: n.raise_error())
|
||||
|
||||
for (i, n) in must_be_imm:
|
||||
self.failUnlessRaises(MustBeDeepImmutableError, lambda: n.raise_error())
|
||||
|
||||
for (i, n) in bad_uri:
|
||||
self.failUnlessRaises(uri.BadURIError, lambda: n.raise_error())
|
||||
|
||||
for (i, n) in ok:
|
||||
self.failIf(n.get_readonly_uri() is None, i)
|
||||
|
||||
for (i, n) in ro_prefixed:
|
||||
self.failUnless(n.get_readonly_uri().startswith("ro."), i)
|
||||
|
||||
for (i, n) in imm_prefixed:
|
||||
self.failUnless(n.get_readonly_uri().startswith("imm."), i)
|
||||
|
||||
|
||||
class DeepStats(unittest.TestCase):
|
||||
timeout = 240 # It takes longer than 120 seconds on Francois's arm box.
|
||||
def test_stats(self):
|
||||
|
@ -41,14 +41,21 @@ class Node(unittest.TestCase):
|
||||
self.failUnlessEqual(fn1.get_readcap(), u)
|
||||
self.failUnlessEqual(fn1.is_readonly(), True)
|
||||
self.failUnlessEqual(fn1.is_mutable(), False)
|
||||
self.failUnlessEqual(fn1.is_unknown(), False)
|
||||
self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True)
|
||||
self.failUnlessEqual(fn1.get_write_uri(), None)
|
||||
self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string())
|
||||
self.failUnlessEqual(fn1.get_size(), 1000)
|
||||
self.failUnlessEqual(fn1.get_storage_index(), u.storage_index)
|
||||
fn1.raise_error()
|
||||
fn2.raise_error()
|
||||
d = {}
|
||||
d[fn1] = 1 # exercise __hash__
|
||||
v = fn1.get_verify_cap()
|
||||
self.failUnless(isinstance(v, uri.CHKFileVerifierURI))
|
||||
self.failUnlessEqual(fn1.get_repair_cap(), v)
|
||||
self.failUnlessEqual(v.is_readonly(), True)
|
||||
self.failUnlessEqual(v.is_mutable(), False)
|
||||
|
||||
|
||||
def test_literal_filenode(self):
|
||||
@ -64,9 +71,14 @@ class Node(unittest.TestCase):
|
||||
self.failUnlessEqual(fn1.get_readcap(), u)
|
||||
self.failUnlessEqual(fn1.is_readonly(), True)
|
||||
self.failUnlessEqual(fn1.is_mutable(), False)
|
||||
self.failUnlessEqual(fn1.is_unknown(), False)
|
||||
self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True)
|
||||
self.failUnlessEqual(fn1.get_write_uri(), None)
|
||||
self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string())
|
||||
self.failUnlessEqual(fn1.get_size(), len(DATA))
|
||||
self.failUnlessEqual(fn1.get_storage_index(), None)
|
||||
fn1.raise_error()
|
||||
fn2.raise_error()
|
||||
d = {}
|
||||
d[fn1] = 1 # exercise __hash__
|
||||
|
||||
@ -99,24 +111,29 @@ class Node(unittest.TestCase):
|
||||
self.failUnlessEqual(n.get_writekey(), wk)
|
||||
self.failUnlessEqual(n.get_readkey(), rk)
|
||||
self.failUnlessEqual(n.get_storage_index(), si)
|
||||
# these itmes are populated on first read (or create), so until that
|
||||
# these items are populated on first read (or create), so until that
|
||||
# happens they'll be None
|
||||
self.failUnlessEqual(n.get_privkey(), None)
|
||||
self.failUnlessEqual(n.get_encprivkey(), None)
|
||||
self.failUnlessEqual(n.get_pubkey(), None)
|
||||
|
||||
self.failUnlessEqual(n.get_uri(), u.to_string())
|
||||
self.failUnlessEqual(n.get_write_uri(), u.to_string())
|
||||
self.failUnlessEqual(n.get_readonly_uri(), u.get_readonly().to_string())
|
||||
self.failUnlessEqual(n.get_cap(), u)
|
||||
self.failUnlessEqual(n.get_readcap(), u.get_readonly())
|
||||
self.failUnlessEqual(n.is_mutable(), True)
|
||||
self.failUnlessEqual(n.is_readonly(), False)
|
||||
self.failUnlessEqual(n.is_unknown(), False)
|
||||
self.failUnlessEqual(n.is_allowed_in_immutable_directory(), False)
|
||||
n.raise_error()
|
||||
|
||||
n2 = MutableFileNode(None, None, client.get_encoding_parameters(),
|
||||
None).init_from_cap(u)
|
||||
self.failUnlessEqual(n, n2)
|
||||
self.failIfEqual(n, "not even the right type")
|
||||
self.failIfEqual(n, u) # not the right class
|
||||
n.raise_error()
|
||||
d = {n: "can these be used as dictionary keys?"}
|
||||
d[n2] = "replace the old one"
|
||||
self.failUnlessEqual(len(d), 1)
|
||||
@ -127,12 +144,16 @@ class Node(unittest.TestCase):
|
||||
self.failUnlessEqual(nro.get_readonly(), nro)
|
||||
self.failUnlessEqual(nro.get_cap(), u.get_readonly())
|
||||
self.failUnlessEqual(nro.get_readcap(), u.get_readonly())
|
||||
self.failUnlessEqual(nro.is_mutable(), True)
|
||||
self.failUnlessEqual(nro.is_readonly(), True)
|
||||
self.failUnlessEqual(nro.is_unknown(), False)
|
||||
self.failUnlessEqual(nro.is_allowed_in_immutable_directory(), False)
|
||||
nro_u = nro.get_uri()
|
||||
self.failUnlessEqual(nro_u, nro.get_readonly_uri())
|
||||
self.failUnlessEqual(nro_u, u.get_readonly().to_string())
|
||||
self.failUnlessEqual(nro.is_mutable(), True)
|
||||
self.failUnlessEqual(nro.is_readonly(), True)
|
||||
self.failUnlessEqual(nro.get_write_uri(), None)
|
||||
self.failUnlessEqual(nro.get_repair_cap(), None) # RSAmut needs writecap
|
||||
nro.raise_error()
|
||||
|
||||
v = n.get_verify_cap()
|
||||
self.failUnless(isinstance(v, uri.SSKVerifierURI))
|
||||
|
@ -17,7 +17,7 @@ from allmydata.scripts import runner
|
||||
from allmydata.interfaces import IDirectoryNode, IFileNode, \
|
||||
NoSuchChildError, NoSharesError
|
||||
from allmydata.monitor import Monitor
|
||||
from allmydata.mutable.common import NotMutableError
|
||||
from allmydata.mutable.common import NotWriteableError
|
||||
from allmydata.mutable import layout as mutable_layout
|
||||
from foolscap.api import DeadReferenceError
|
||||
from twisted.python.failure import Failure
|
||||
@ -890,11 +890,11 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
|
||||
d1.addCallback(lambda res: dirnode.list())
|
||||
d1.addCallback(self.log, "dirnode.list")
|
||||
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope"))
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope"))
|
||||
|
||||
d1.addCallback(self.log, "doing add_file(ro)")
|
||||
ut = upload.Data("I will disappear, unrecorded and unobserved. The tragedy of my demise is made more poignant by its silence, but this beauty is not for you to ever know.", convergence="99i-p1x4-xd4-18yc-ywt-87uu-msu-zo -- completely and totally unguessable string (unless you read this)")
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut))
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut))
|
||||
|
||||
d1.addCallback(self.log, "doing get(ro)")
|
||||
d1.addCallback(lambda res: dirnode.get(u"mydata992"))
|
||||
@ -902,17 +902,17 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
|
||||
self.failUnless(IFileNode.providedBy(filenode)))
|
||||
|
||||
d1.addCallback(self.log, "doing delete(ro)")
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "delete(nope)", None, dirnode.delete, u"mydata992"))
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "delete(nope)", None, dirnode.delete, u"mydata992"))
|
||||
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri, self.uri))
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri, self.uri))
|
||||
|
||||
d1.addCallback(lambda res: self.shouldFail2(NoSuchChildError, "get(missing)", "missing", dirnode.get, u"missing"))
|
||||
|
||||
personal = self._personal_node
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope"))
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope"))
|
||||
|
||||
d1.addCallback(self.log, "doing move_child_to(ro)2")
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope"))
|
||||
d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope"))
|
||||
|
||||
d1.addCallback(self.log, "finished with _got_s2ro")
|
||||
return d1
|
||||
|
@ -3,7 +3,7 @@ from twisted.trial import unittest
|
||||
from allmydata import uri
|
||||
from allmydata.util import hashutil, base32
|
||||
from allmydata.interfaces import IURI, IFileURI, IDirnodeURI, IMutableFileURI, \
|
||||
IVerifierURI
|
||||
IVerifierURI, CapConstraintError
|
||||
|
||||
class Literal(unittest.TestCase):
|
||||
def _help_test(self, data):
|
||||
@ -22,8 +22,16 @@ class Literal(unittest.TestCase):
|
||||
self.failIf(IDirnodeURI.providedBy(u2))
|
||||
self.failUnlessEqual(u2.data, data)
|
||||
self.failUnlessEqual(u2.get_size(), len(data))
|
||||
self.failUnless(u.is_readonly())
|
||||
self.failIf(u.is_mutable())
|
||||
self.failUnless(u2.is_readonly())
|
||||
self.failIf(u2.is_mutable())
|
||||
|
||||
u2i = uri.from_string(u.to_string(), deep_immutable=True)
|
||||
self.failUnless(IFileURI.providedBy(u2i))
|
||||
self.failIf(IDirnodeURI.providedBy(u2i))
|
||||
self.failUnlessEqual(u2i.data, data)
|
||||
self.failUnlessEqual(u2i.get_size(), len(data))
|
||||
self.failUnless(u2i.is_readonly())
|
||||
self.failIf(u2i.is_mutable())
|
||||
|
||||
u3 = u.get_readonly()
|
||||
self.failUnlessIdentical(u, u3)
|
||||
@ -51,18 +59,36 @@ class Compare(unittest.TestCase):
|
||||
fileURI = 'URI:CHK:f5ahxa25t4qkktywz6teyfvcx4:opuioq7tj2y6idzfp6cazehtmgs5fdcebcz3cygrxyydvcozrmeq:3:10:345834'
|
||||
chk1 = uri.CHKFileURI.init_from_string(fileURI)
|
||||
chk2 = uri.CHKFileURI.init_from_string(fileURI)
|
||||
unk = uri.UnknownURI("lafs://from_the_future")
|
||||
self.failIfEqual(lit1, chk1)
|
||||
self.failUnlessEqual(chk1, chk2)
|
||||
self.failIfEqual(chk1, "not actually a URI")
|
||||
# these should be hashable too
|
||||
s = set([lit1, chk1, chk2])
|
||||
self.failUnlessEqual(len(s), 2) # since chk1==chk2
|
||||
s = set([lit1, chk1, chk2, unk])
|
||||
self.failUnlessEqual(len(s), 3) # since chk1==chk2
|
||||
|
||||
def test_is_uri(self):
|
||||
lit1 = uri.LiteralFileURI("some data").to_string()
|
||||
self.failUnless(uri.is_uri(lit1))
|
||||
self.failIf(uri.is_uri(None))
|
||||
|
||||
def test_is_literal_file_uri(self):
|
||||
lit1 = uri.LiteralFileURI("some data").to_string()
|
||||
self.failUnless(uri.is_literal_file_uri(lit1))
|
||||
self.failIf(uri.is_literal_file_uri(None))
|
||||
self.failIf(uri.is_literal_file_uri("foo"))
|
||||
self.failIf(uri.is_literal_file_uri("ro.foo"))
|
||||
self.failIf(uri.is_literal_file_uri("URI:LITfoo"))
|
||||
self.failUnless(uri.is_literal_file_uri("ro.URI:LIT:foo"))
|
||||
self.failUnless(uri.is_literal_file_uri("imm.URI:LIT:foo"))
|
||||
|
||||
def test_has_uri_prefix(self):
|
||||
self.failUnless(uri.has_uri_prefix("URI:foo"))
|
||||
self.failUnless(uri.has_uri_prefix("ro.URI:foo"))
|
||||
self.failUnless(uri.has_uri_prefix("imm.URI:foo"))
|
||||
self.failIf(uri.has_uri_prefix(None))
|
||||
self.failIf(uri.has_uri_prefix("foo"))
|
||||
|
||||
class CHKFile(unittest.TestCase):
|
||||
def test_pack(self):
|
||||
key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
|
||||
@ -88,8 +114,7 @@ class CHKFile(unittest.TestCase):
|
||||
self.failUnless(IFileURI.providedBy(u))
|
||||
self.failIf(IDirnodeURI.providedBy(u))
|
||||
self.failUnlessEqual(u.get_size(), 1234)
|
||||
self.failUnless(u.is_readonly())
|
||||
self.failIf(u.is_mutable())
|
||||
|
||||
u_ro = u.get_readonly()
|
||||
self.failUnlessIdentical(u, u_ro)
|
||||
he = u.to_human_encoding()
|
||||
@ -109,11 +134,19 @@ class CHKFile(unittest.TestCase):
|
||||
self.failUnless(IFileURI.providedBy(u2))
|
||||
self.failIf(IDirnodeURI.providedBy(u2))
|
||||
self.failUnlessEqual(u2.get_size(), 1234)
|
||||
self.failUnless(u2.is_readonly())
|
||||
self.failIf(u2.is_mutable())
|
||||
|
||||
u2i = uri.from_string(u.to_string(), deep_immutable=True)
|
||||
self.failUnlessEqual(u.to_string(), u2i.to_string())
|
||||
u2ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u.to_string())
|
||||
self.failUnlessEqual(u.to_string(), u2ro.to_string())
|
||||
u2imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u.to_string())
|
||||
self.failUnlessEqual(u.to_string(), u2imm.to_string())
|
||||
|
||||
v = u.get_verify_cap()
|
||||
self.failUnless(isinstance(v.to_string(), str))
|
||||
self.failUnless(v.is_readonly())
|
||||
self.failIf(v.is_mutable())
|
||||
|
||||
v2 = uri.from_string(v.to_string())
|
||||
self.failUnlessEqual(v, v2)
|
||||
he = v.to_human_encoding()
|
||||
@ -126,6 +159,8 @@ class CHKFile(unittest.TestCase):
|
||||
total_shares=10,
|
||||
size=1234)
|
||||
self.failUnless(isinstance(v3.to_string(), str))
|
||||
self.failUnless(v3.is_readonly())
|
||||
self.failIf(v3.is_mutable())
|
||||
|
||||
def test_pack_badly(self):
|
||||
key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
|
||||
@ -179,13 +214,20 @@ class Extension(unittest.TestCase):
|
||||
self.failUnlessEqual(readable["UEB_hash"],
|
||||
base32.b2a(hashutil.uri_extension_hash(ext)))
|
||||
|
||||
class Invalid(unittest.TestCase):
|
||||
class Unknown(unittest.TestCase):
|
||||
def test_from_future(self):
|
||||
# any URI type that we don't recognize should be treated as unknown
|
||||
future_uri = "I am a URI from the future. Whatever you do, don't "
|
||||
u = uri.from_string(future_uri)
|
||||
self.failUnless(isinstance(u, uri.UnknownURI))
|
||||
self.failUnlessEqual(u.to_string(), future_uri)
|
||||
self.failUnless(u.get_readonly() is None)
|
||||
self.failUnless(u.get_error() is None)
|
||||
|
||||
u2 = uri.UnknownURI(future_uri, error=CapConstraintError("..."))
|
||||
self.failUnlessEqual(u.to_string(), future_uri)
|
||||
self.failUnless(u2.get_readonly() is None)
|
||||
self.failUnless(isinstance(u2.get_error(), CapConstraintError))
|
||||
|
||||
class Constraint(unittest.TestCase):
|
||||
def test_constraint(self):
|
||||
@ -226,6 +268,13 @@ class Mutable(unittest.TestCase):
|
||||
self.failUnless(IMutableFileURI.providedBy(u2))
|
||||
self.failIf(IDirnodeURI.providedBy(u2))
|
||||
|
||||
u2i = uri.from_string(u.to_string(), deep_immutable=True)
|
||||
self.failUnless(isinstance(u2i, uri.UnknownURI), u2i)
|
||||
u2ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u.to_string())
|
||||
self.failUnless(isinstance(u2ro, uri.UnknownURI), u2ro)
|
||||
u2imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u.to_string())
|
||||
self.failUnless(isinstance(u2imm, uri.UnknownURI), u2imm)
|
||||
|
||||
u3 = u2.get_readonly()
|
||||
readkey = hashutil.ssk_readkey_hash(writekey)
|
||||
self.failUnlessEqual(u3.fingerprint, fingerprint)
|
||||
@ -236,6 +285,13 @@ class Mutable(unittest.TestCase):
|
||||
self.failUnless(IMutableFileURI.providedBy(u3))
|
||||
self.failIf(IDirnodeURI.providedBy(u3))
|
||||
|
||||
u3i = uri.from_string(u3.to_string(), deep_immutable=True)
|
||||
self.failUnless(isinstance(u3i, uri.UnknownURI), u3i)
|
||||
u3ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u3.to_string())
|
||||
self.failUnlessEqual(u3.to_string(), u3ro.to_string())
|
||||
u3imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u3.to_string())
|
||||
self.failUnless(isinstance(u3imm, uri.UnknownURI), u3imm)
|
||||
|
||||
he = u3.to_human_encoding()
|
||||
u3_h = uri.ReadonlySSKFileURI.init_from_human_encoding(he)
|
||||
self.failUnlessEqual(u3, u3_h)
|
||||
@ -249,6 +305,13 @@ class Mutable(unittest.TestCase):
|
||||
self.failUnless(IMutableFileURI.providedBy(u4))
|
||||
self.failIf(IDirnodeURI.providedBy(u4))
|
||||
|
||||
u4i = uri.from_string(u4.to_string(), deep_immutable=True)
|
||||
self.failUnless(isinstance(u4i, uri.UnknownURI), u4i)
|
||||
u4ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u4.to_string())
|
||||
self.failUnlessEqual(u4.to_string(), u4ro.to_string())
|
||||
u4imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u4.to_string())
|
||||
self.failUnless(isinstance(u4imm, uri.UnknownURI), u4imm)
|
||||
|
||||
u4a = uri.from_string(u4.to_string())
|
||||
self.failUnlessEqual(u4a, u4)
|
||||
self.failUnless("ReadonlySSKFileURI" in str(u4a))
|
||||
@ -291,12 +354,19 @@ class Dirnode(unittest.TestCase):
|
||||
self.failIf(IFileURI.providedBy(u2))
|
||||
self.failUnless(IDirnodeURI.providedBy(u2))
|
||||
|
||||
u2i = uri.from_string(u1.to_string(), deep_immutable=True)
|
||||
self.failUnless(isinstance(u2i, uri.UnknownURI))
|
||||
|
||||
u3 = u2.get_readonly()
|
||||
self.failUnless(u3.is_readonly())
|
||||
self.failUnless(u3.is_mutable())
|
||||
self.failUnless(IURI.providedBy(u3))
|
||||
self.failIf(IFileURI.providedBy(u3))
|
||||
self.failUnless(IDirnodeURI.providedBy(u3))
|
||||
|
||||
u3i = uri.from_string(u2.to_string(), deep_immutable=True)
|
||||
self.failUnless(isinstance(u3i, uri.UnknownURI))
|
||||
|
||||
u3n = u3._filenode_uri
|
||||
self.failUnless(u3n.is_readonly())
|
||||
self.failUnless(u3n.is_mutable())
|
||||
@ -363,10 +433,16 @@ class Dirnode(unittest.TestCase):
|
||||
self.failIf(IFileURI.providedBy(u2))
|
||||
self.failUnless(IDirnodeURI.providedBy(u2))
|
||||
|
||||
u2i = uri.from_string(u1.to_string(), deep_immutable=True)
|
||||
self.failUnlessEqual(u1.to_string(), u2i.to_string())
|
||||
|
||||
u3 = u2.get_readonly()
|
||||
self.failUnlessEqual(u3.to_string(), u2.to_string())
|
||||
self.failUnless(str(u3))
|
||||
|
||||
u3i = uri.from_string(u2.to_string(), deep_immutable=True)
|
||||
self.failUnlessEqual(u2.to_string(), u3i.to_string())
|
||||
|
||||
u2_verifier = u2.get_verify_cap()
|
||||
self.failUnless(isinstance(u2_verifier,
|
||||
uri.ImmutableDirectoryURIVerifier),
|
||||
|
@ -7,7 +7,7 @@ from twisted.internet import defer, reactor
|
||||
from twisted.web import client, error, http
|
||||
from twisted.python import failure, log
|
||||
from nevow import rend
|
||||
from allmydata import interfaces, uri, webish
|
||||
from allmydata import interfaces, uri, webish, dirnode
|
||||
from allmydata.storage.shares import get_share_file
|
||||
from allmydata.storage_client import StorageFarmBroker
|
||||
from allmydata.immutable import upload, download
|
||||
@ -18,6 +18,7 @@ from allmydata.web import status, common
|
||||
from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
|
||||
from allmydata.util import fileutil, base32
|
||||
from allmydata.util.consumer import download_to_data
|
||||
from allmydata.util.netstring import split_netstring
|
||||
from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
|
||||
create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
|
||||
from allmydata.interfaces import IMutableFileNode
|
||||
@ -735,7 +736,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
|
||||
# TODO: we lose the response code, so we can't check this
|
||||
#self.failUnlessEqual(responsecode, 201)
|
||||
d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
|
||||
self.NEWFILE_CONTENTS))
|
||||
@ -746,7 +747,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
self.NEWFILE_CONTENTS)
|
||||
# TODO: we lose the response code, so we can't check this
|
||||
#self.failUnlessEqual(responsecode, 201)
|
||||
d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
|
||||
self.NEWFILE_CONTENTS))
|
||||
@ -776,7 +777,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
self.failIf(u.is_readonly())
|
||||
return res
|
||||
d.addCallback(_check_uri)
|
||||
d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessMutableChildContentsAre(self._foo_node,
|
||||
u"new.txt",
|
||||
@ -796,7 +797,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
|
||||
# TODO: we lose the response code, so we can't check this
|
||||
#self.failUnlessEqual(responsecode, 200)
|
||||
d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
|
||||
self.NEWFILE_CONTENTS))
|
||||
@ -821,7 +822,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
def test_PUT_NEWFILEURL_mkdirs(self):
|
||||
d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
|
||||
fn = self._foo_node
|
||||
d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
|
||||
d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
|
||||
d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
|
||||
d.addCallback(lambda res:
|
||||
@ -954,7 +955,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
self.failUnless(re.search(get_sub, res), res)
|
||||
d.addCallback(_check)
|
||||
|
||||
# look at a directory which is readonly
|
||||
# look at a readonly directory
|
||||
d.addCallback(lambda res:
|
||||
self.GET(self.public_url + "/reedownlee", followRedirect=True))
|
||||
def _check2(res):
|
||||
@ -1167,23 +1168,33 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
return d
|
||||
|
||||
def test_POST_NEWDIRURL_initial_children(self):
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
(newkids, caps) = self._create_initial_children()
|
||||
d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
|
||||
simplejson.dumps(newkids))
|
||||
def _check(uri):
|
||||
n = self.s.create_node_from_uri(uri.strip())
|
||||
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
|
||||
self.failUnlessROChildURIIs(n, u"child-imm",
|
||||
caps['filecap1']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-mutable",
|
||||
filecap2))
|
||||
self.failUnlessRWChildURIIs(n, u"child-mutable",
|
||||
caps['filecap2']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-mutable-ro",
|
||||
filecap3))
|
||||
self.failUnlessROChildURIIs(n, u"child-mutable-ro",
|
||||
caps['filecap3']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"dirchild", dircap))
|
||||
self.failUnlessROChildURIIs(n, u"unknownchild-ro",
|
||||
caps['unknown_rocap']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
|
||||
caps['unknown_rwcap']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessROChildURIIs(n, u"unknownchild-imm",
|
||||
caps['unknown_immcap']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessRWChildURIIs(n, u"dirchild",
|
||||
caps['dircap']))
|
||||
return d2
|
||||
d.addCallback(_check)
|
||||
d.addCallback(lambda res:
|
||||
@ -1191,21 +1202,25 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
|
||||
d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
|
||||
return d
|
||||
|
||||
def test_POST_NEWDIRURL_immutable(self):
|
||||
(newkids, filecap1, immdircap) = self._create_immutable_children()
|
||||
(newkids, caps) = self._create_immutable_children()
|
||||
d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
|
||||
simplejson.dumps(newkids))
|
||||
def _check(uri):
|
||||
n = self.s.create_node_from_uri(uri.strip())
|
||||
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
|
||||
self.failUnlessROChildURIIs(n, u"child-imm",
|
||||
caps['filecap1']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"dirchild-imm",
|
||||
immdircap))
|
||||
self.failUnlessROChildURIIs(n, u"unknownchild-imm",
|
||||
caps['unknown_immcap']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessROChildURIIs(n, u"dirchild-imm",
|
||||
caps['immdircap']))
|
||||
return d2
|
||||
d.addCallback(_check)
|
||||
d.addCallback(lambda res:
|
||||
@ -1213,18 +1228,19 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
|
||||
d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap)
|
||||
d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
|
||||
d.addErrback(self.explain_web_error)
|
||||
return d
|
||||
|
||||
def test_POST_NEWDIRURL_immutable_bad(self):
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
(newkids, caps) = self._create_initial_children()
|
||||
d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
|
||||
"400 Bad Request",
|
||||
"a mkdir-immutable operation was given a child that was not itself immutable",
|
||||
"needed to be immutable but was not",
|
||||
self.POST2,
|
||||
self.public_url + "/foo/newdir?t=mkdir-immutable",
|
||||
simplejson.dumps(newkids))
|
||||
@ -1346,19 +1362,51 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d.addCallback(_check)
|
||||
return d
|
||||
|
||||
def failUnlessChildURIIs(self, node, name, expected_uri):
|
||||
def failUnlessRWChildURIIs(self, node, name, expected_uri):
|
||||
assert isinstance(name, unicode)
|
||||
d = node.get_child_at_path(name)
|
||||
def _check(child):
|
||||
self.failUnless(child.is_unknown() or not child.is_readonly())
|
||||
self.failUnlessEqual(child.get_uri(), expected_uri.strip())
|
||||
self.failUnlessEqual(child.get_write_uri(), expected_uri.strip())
|
||||
expected_ro_uri = self._make_readonly(expected_uri)
|
||||
if expected_ro_uri:
|
||||
self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
|
||||
d.addCallback(_check)
|
||||
return d
|
||||
|
||||
def failUnlessURIMatchesChild(self, got_uri, node, name):
|
||||
def failUnlessROChildURIIs(self, node, name, expected_uri):
|
||||
assert isinstance(name, unicode)
|
||||
d = node.get_child_at_path(name)
|
||||
def _check(child):
|
||||
self.failUnless(child.is_unknown() or child.is_readonly())
|
||||
self.failUnlessEqual(child.get_write_uri(), None)
|
||||
self.failUnlessEqual(child.get_uri(), expected_uri.strip())
|
||||
self.failUnlessEqual(child.get_readonly_uri(), expected_uri.strip())
|
||||
d.addCallback(_check)
|
||||
return d
|
||||
|
||||
def failUnlessURIMatchesRWChild(self, got_uri, node, name):
|
||||
assert isinstance(name, unicode)
|
||||
d = node.get_child_at_path(name)
|
||||
def _check(child):
|
||||
self.failUnless(child.is_unknown() or not child.is_readonly())
|
||||
self.failUnlessEqual(child.get_uri(), got_uri.strip())
|
||||
self.failUnlessEqual(child.get_write_uri(), got_uri.strip())
|
||||
expected_ro_uri = self._make_readonly(got_uri)
|
||||
if expected_ro_uri:
|
||||
self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
|
||||
d.addCallback(_check)
|
||||
return d
|
||||
|
||||
def failUnlessURIMatchesROChild(self, got_uri, node, name):
|
||||
assert isinstance(name, unicode)
|
||||
d = node.get_child_at_path(name)
|
||||
def _check(child):
|
||||
self.failUnless(child.is_unknown() or child.is_readonly())
|
||||
self.failUnlessEqual(child.get_write_uri(), None)
|
||||
self.failUnlessEqual(got_uri.strip(), child.get_uri())
|
||||
self.failUnlessEqual(got_uri.strip(), child.get_readonly_uri())
|
||||
d.addCallback(_check)
|
||||
return d
|
||||
|
||||
@ -1369,7 +1417,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d = self.POST(self.public_url + "/foo", t="upload",
|
||||
file=("new.txt", self.NEWFILE_CONTENTS))
|
||||
fn = self._foo_node
|
||||
d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildContentsAre(fn, u"new.txt",
|
||||
self.NEWFILE_CONTENTS))
|
||||
@ -1380,7 +1428,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d = self.POST(self.public_url + "/foo", t="upload",
|
||||
file=(filename, self.NEWFILE_CONTENTS))
|
||||
fn = self._foo_node
|
||||
d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildContentsAre(fn, filename,
|
||||
self.NEWFILE_CONTENTS))
|
||||
@ -1397,7 +1445,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
name=filename,
|
||||
file=("overridden", self.NEWFILE_CONTENTS))
|
||||
fn = self._foo_node
|
||||
d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildContentsAre(fn, filename,
|
||||
self.NEWFILE_CONTENTS))
|
||||
@ -1499,7 +1547,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
|
||||
file=("new.txt", self.NEWFILE_CONTENTS))
|
||||
fn = self._foo_node
|
||||
d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessMutableChildContentsAre(fn, u"new.txt",
|
||||
self.NEWFILE_CONTENTS))
|
||||
@ -1518,7 +1566,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
self.POST(self.public_url + "/foo", t="upload",
|
||||
mutable="true",
|
||||
file=("new.txt", NEWER_CONTENTS)))
|
||||
d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessMutableChildContentsAre(fn, u"new.txt",
|
||||
NEWER_CONTENTS))
|
||||
@ -1534,7 +1582,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
|
||||
d.addCallback(lambda res:
|
||||
self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
|
||||
d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessMutableChildContentsAre(fn, u"new.txt",
|
||||
NEW2_CONTENTS))
|
||||
@ -1663,7 +1711,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d = self.POST(self.public_url + "/foo", t="upload",
|
||||
file=("bar.txt", self.NEWFILE_CONTENTS))
|
||||
fn = self._foo_node
|
||||
d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildContentsAre(fn, u"bar.txt",
|
||||
self.NEWFILE_CONTENTS))
|
||||
@ -1714,7 +1762,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
fn = self._foo_node
|
||||
d = self.POST(self.public_url + "/foo", t="upload",
|
||||
name="new.txt", file=self.NEWFILE_CONTENTS)
|
||||
d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildContentsAre(fn, u"new.txt",
|
||||
self.NEWFILE_CONTENTS))
|
||||
@ -1977,7 +2025,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
return d
|
||||
|
||||
def test_POST_mkdir_initial_children(self):
|
||||
newkids, filecap1, ign, ign, ign = self._create_initial_children()
|
||||
(newkids, caps) = self._create_initial_children()
|
||||
d = self.POST2(self.public_url +
|
||||
"/foo?t=mkdir-with-children&name=newdir",
|
||||
simplejson.dumps(newkids))
|
||||
@ -1986,11 +2034,11 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
|
||||
d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
|
||||
return d
|
||||
|
||||
def test_POST_mkdir_immutable(self):
|
||||
(newkids, filecap1, immdircap) = self._create_immutable_children()
|
||||
(newkids, caps) = self._create_immutable_children()
|
||||
d = self.POST2(self.public_url +
|
||||
"/foo?t=mkdir-immutable&name=newdir",
|
||||
simplejson.dumps(newkids))
|
||||
@ -1999,17 +2047,18 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
|
||||
d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap)
|
||||
d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
|
||||
return d
|
||||
|
||||
def test_POST_mkdir_immutable_bad(self):
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
(newkids, caps) = self._create_initial_children()
|
||||
d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
|
||||
"400 Bad Request",
|
||||
"a mkdir-immutable operation was given a child that was not itself immutable",
|
||||
"needed to be immutable but was not",
|
||||
self.POST2,
|
||||
self.public_url +
|
||||
"/foo?t=mkdir-immutable&name=newdir",
|
||||
@ -2068,21 +2117,43 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d.addErrback(self.explain_web_error)
|
||||
return d
|
||||
|
||||
def _make_readonly(self, u):
|
||||
ro_uri = uri.from_string(u).get_readonly()
|
||||
if ro_uri is None:
|
||||
return None
|
||||
return ro_uri.to_string()
|
||||
|
||||
def _create_initial_children(self):
|
||||
contents, n, filecap1 = self.makefile(12)
|
||||
md1 = {"metakey1": "metavalue1"}
|
||||
filecap2 = make_mutable_file_uri()
|
||||
node3 = self.s.create_node_from_uri(make_mutable_file_uri())
|
||||
filecap3 = node3.get_readonly_uri()
|
||||
unknown_rwcap = "lafs://from_the_future"
|
||||
unknown_rocap = "ro.lafs://readonly_from_the_future"
|
||||
unknown_immcap = "imm.lafs://immutable_from_the_future"
|
||||
node4 = self.s.create_node_from_uri(make_mutable_file_uri())
|
||||
dircap = DirectoryNode(node4, None, None).get_uri()
|
||||
newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
|
||||
"metadata": md1, }],
|
||||
u"child-mutable": ["filenode", {"rw_uri": filecap2}],
|
||||
newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
|
||||
"ro_uri": self._make_readonly(filecap1),
|
||||
"metadata": md1, }],
|
||||
u"child-mutable": ["filenode", {"rw_uri": filecap2,
|
||||
"ro_uri": self._make_readonly(filecap2)}],
|
||||
u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
|
||||
u"dirchild": ["dirnode", {"rw_uri": dircap}],
|
||||
u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
|
||||
"ro_uri": unknown_rocap}],
|
||||
u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
|
||||
u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
|
||||
u"dirchild": ["dirnode", {"rw_uri": dircap,
|
||||
"ro_uri": self._make_readonly(dircap)}],
|
||||
}
|
||||
return newkids, filecap1, filecap2, filecap3, dircap
|
||||
return newkids, {'filecap1': filecap1,
|
||||
'filecap2': filecap2,
|
||||
'filecap3': filecap3,
|
||||
'unknown_rwcap': unknown_rwcap,
|
||||
'unknown_rocap': unknown_rocap,
|
||||
'unknown_immcap': unknown_immcap,
|
||||
'dircap': dircap}
|
||||
|
||||
def _create_immutable_children(self):
|
||||
contents, n, filecap1 = self.makefile(12)
|
||||
@ -2090,31 +2161,45 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
tnode = create_chk_filenode("immutable directory contents\n"*10)
|
||||
dnode = DirectoryNode(tnode, None, None)
|
||||
assert not dnode.is_mutable()
|
||||
unknown_immcap = "imm.lafs://immutable_from_the_future"
|
||||
immdircap = dnode.get_uri()
|
||||
newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
|
||||
"metadata": md1, }],
|
||||
u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
|
||||
newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
|
||||
"metadata": md1, }],
|
||||
u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
|
||||
u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
|
||||
}
|
||||
return newkids, filecap1, immdircap
|
||||
return newkids, {'filecap1': filecap1,
|
||||
'unknown_immcap': unknown_immcap,
|
||||
'immdircap': immdircap}
|
||||
|
||||
def test_POST_mkdir_no_parentdir_initial_children(self):
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
(newkids, caps) = self._create_initial_children()
|
||||
d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
|
||||
def _after_mkdir(res):
|
||||
self.failUnless(res.startswith("URI:DIR"), res)
|
||||
n = self.s.create_node_from_uri(res)
|
||||
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
|
||||
self.failUnlessROChildURIIs(n, u"child-imm",
|
||||
caps['filecap1']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-mutable",
|
||||
filecap2))
|
||||
self.failUnlessRWChildURIIs(n, u"child-mutable",
|
||||
caps['filecap2']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-mutable-ro",
|
||||
filecap3))
|
||||
self.failUnlessROChildURIIs(n, u"child-mutable-ro",
|
||||
caps['filecap3']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"dirchild", dircap))
|
||||
self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
|
||||
caps['unknown_rwcap']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessROChildURIIs(n, u"unknownchild-ro",
|
||||
caps['unknown_rocap']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessROChildURIIs(n, u"unknownchild-imm",
|
||||
caps['unknown_immcap']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessRWChildURIIs(n, u"dirchild",
|
||||
caps['dircap']))
|
||||
return d2
|
||||
d.addCallback(_after_mkdir)
|
||||
return d
|
||||
@ -2122,8 +2207,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
def test_POST_mkdir_no_parentdir_unexpected_children(self):
|
||||
# the regular /uri?t=mkdir operation is specified to ignore its body.
|
||||
# Only t=mkdir-with-children pays attention to it.
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
(newkids, caps) = self._create_initial_children()
|
||||
d = self.shouldHTTPError("POST t=mkdir unexpected children",
|
||||
400, "Bad Request",
|
||||
"t=mkdir does not accept children=, "
|
||||
@ -2140,28 +2224,31 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
return d
|
||||
|
||||
def test_POST_mkdir_no_parentdir_immutable(self):
|
||||
(newkids, filecap1, immdircap) = self._create_immutable_children()
|
||||
(newkids, caps) = self._create_immutable_children()
|
||||
d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
|
||||
def _after_mkdir(res):
|
||||
self.failUnless(res.startswith("URI:DIR"), res)
|
||||
n = self.s.create_node_from_uri(res)
|
||||
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
|
||||
self.failUnlessROChildURIIs(n, u"child-imm",
|
||||
caps['filecap1']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"dirchild-imm",
|
||||
immdircap))
|
||||
self.failUnlessROChildURIIs(n, u"unknownchild-imm",
|
||||
caps['unknown_immcap']))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessROChildURIIs(n, u"dirchild-imm",
|
||||
caps['immdircap']))
|
||||
return d2
|
||||
d.addCallback(_after_mkdir)
|
||||
return d
|
||||
|
||||
def test_POST_mkdir_no_parentdir_immutable_bad(self):
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
(newkids, caps) = self._create_initial_children()
|
||||
d = self.shouldFail2(error.Error,
|
||||
"test_POST_mkdir_no_parentdir_immutable_bad",
|
||||
"400 Bad Request",
|
||||
"a mkdir-immutable operation was given a child that was not itself immutable",
|
||||
"needed to be immutable but was not",
|
||||
self.POST2,
|
||||
"/uri?t=mkdir-immutable",
|
||||
simplejson.dumps(newkids))
|
||||
@ -2269,9 +2356,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
|
||||
d = client.getPage(url, method="POST", postdata=reqbody)
|
||||
def _then(res):
|
||||
self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
|
||||
self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
|
||||
self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
|
||||
self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
|
||||
self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
|
||||
self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
|
||||
|
||||
d.addCallback(_then)
|
||||
d.addErrback(self.dump_error)
|
||||
@ -2283,7 +2370,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
def test_POST_put_uri(self):
|
||||
contents, n, newuri = self.makefile(8)
|
||||
d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
|
||||
d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
|
||||
contents))
|
||||
@ -2292,7 +2379,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
def test_POST_put_uri_replace(self):
|
||||
contents, n, newuri = self.makefile(8)
|
||||
d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
|
||||
d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
|
||||
d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
|
||||
contents))
|
||||
@ -2521,9 +2608,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessEqual(res.strip(), new_uri))
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildURIIs(self.public_root,
|
||||
u"foo",
|
||||
new_uri))
|
||||
self.failUnlessRWChildURIIs(self.public_root,
|
||||
u"foo",
|
||||
new_uri))
|
||||
return d
|
||||
d.addCallback(_made_dir)
|
||||
return d
|
||||
@ -2540,9 +2627,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
self.public_url + "/foo?t=uri&replace=false",
|
||||
new_uri)
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildURIIs(self.public_root,
|
||||
u"foo",
|
||||
self._foo_uri))
|
||||
self.failUnlessRWChildURIIs(self.public_root,
|
||||
u"foo",
|
||||
self._foo_uri))
|
||||
return d
|
||||
d.addCallback(_made_dir)
|
||||
return d
|
||||
@ -2552,9 +2639,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
"400 Bad Request", "PUT to a directory",
|
||||
self.PUT, self.public_url + "/foo?t=BOGUS", "")
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessChildURIIs(self.public_root,
|
||||
u"foo",
|
||||
self._foo_uri))
|
||||
self.failUnlessRWChildURIIs(self.public_root,
|
||||
u"foo",
|
||||
self._foo_uri))
|
||||
return d
|
||||
|
||||
def test_PUT_NEWFILEURL_uri(self):
|
||||
@ -3081,71 +3168,246 @@ class Grid(GridTestMixin, WebErrorMixin, unittest.TestCase, ShouldFailMixin):
|
||||
d.addErrback(self.explain_web_error)
|
||||
return d
|
||||
|
||||
def test_unknown(self):
|
||||
def test_unknown(self, immutable=False):
|
||||
self.basedir = "web/Grid/unknown"
|
||||
if immutable:
|
||||
self.basedir = "web/Grid/unknown-immutable"
|
||||
|
||||
self.set_up_grid()
|
||||
c0 = self.g.clients[0]
|
||||
self.uris = {}
|
||||
self.fileurls = {}
|
||||
|
||||
future_writecap = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
# the future cap format may contain slashes, which must be tolerated
|
||||
expected_info_url = "uri/%s?t=info" % urllib.quote(future_writecap,
|
||||
expected_info_url = "uri/%s?t=info" % urllib.quote(future_write_uri,
|
||||
safe="")
|
||||
future_node = UnknownNode(future_writecap, future_readcap)
|
||||
|
||||
d = c0.create_dirnode()
|
||||
if immutable:
|
||||
name = u"future-imm"
|
||||
future_node = UnknownNode(None, future_read_uri, deep_immutable=True)
|
||||
d = c0.create_immutable_dirnode({name: (future_node, {})})
|
||||
else:
|
||||
name = u"future"
|
||||
future_node = UnknownNode(future_write_uri, future_read_uri)
|
||||
d = c0.create_dirnode()
|
||||
|
||||
def _stash_root_and_create_file(n):
|
||||
self.rootnode = n
|
||||
self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
|
||||
self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
|
||||
return self.rootnode.set_node(u"future", future_node)
|
||||
if not immutable:
|
||||
return self.rootnode.set_node(name, future_node)
|
||||
d.addCallback(_stash_root_and_create_file)
|
||||
|
||||
# make sure directory listing tolerates unknown nodes
|
||||
d.addCallback(lambda ign: self.GET(self.rooturl))
|
||||
def _check_html(res):
|
||||
self.failUnlessIn("<td>future</td>", res)
|
||||
# find the More Info link for "future", should be relative
|
||||
def _check_directory_html(res):
|
||||
self.failUnlessIn("<td>%s</td>" % (str(name),), res)
|
||||
# find the More Info link for name, should be relative
|
||||
mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
|
||||
info_url = mo.group(1)
|
||||
self.failUnlessEqual(info_url, "future?t=info")
|
||||
self.failUnlessEqual(info_url, "%s?t=info" % (str(name),))
|
||||
d.addCallback(_check_directory_html)
|
||||
|
||||
d.addCallback(_check_html)
|
||||
d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
|
||||
def _check_json(res, expect_writecap):
|
||||
def _check_directory_json(res, expect_rw_uri):
|
||||
data = simplejson.loads(res)
|
||||
self.failUnlessEqual(data[0], "dirnode")
|
||||
f = data[1]["children"]["future"]
|
||||
f = data[1]["children"][name]
|
||||
self.failUnlessEqual(f[0], "unknown")
|
||||
if expect_writecap:
|
||||
self.failUnlessEqual(f[1]["rw_uri"], future_writecap)
|
||||
if expect_rw_uri:
|
||||
self.failUnlessEqual(f[1]["rw_uri"], future_write_uri)
|
||||
else:
|
||||
self.failIfIn("rw_uri", f[1])
|
||||
self.failUnlessEqual(f[1]["ro_uri"], future_readcap)
|
||||
self.failUnlessEqual(f[1]["ro_uri"],
|
||||
("imm." if immutable else "ro.") + future_read_uri)
|
||||
self.failUnless("metadata" in f[1])
|
||||
d.addCallback(_check_json, expect_writecap=True)
|
||||
d.addCallback(lambda ign: self.GET(expected_info_url))
|
||||
def _check_info(res, expect_readcap):
|
||||
d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
|
||||
|
||||
def _check_info(res, expect_rw_uri, expect_ro_uri):
|
||||
self.failUnlessIn("Object Type: <span>unknown</span>", res)
|
||||
self.failUnlessIn(future_writecap, res)
|
||||
if expect_readcap:
|
||||
self.failUnlessIn(future_readcap, res)
|
||||
if expect_rw_uri:
|
||||
self.failUnlessIn(future_write_uri, res)
|
||||
if expect_ro_uri:
|
||||
self.failUnlessIn(future_read_uri, res)
|
||||
else:
|
||||
self.failIfIn(future_read_uri, res)
|
||||
self.failIfIn("Raw data as", res)
|
||||
self.failIfIn("Directory writecap", res)
|
||||
self.failIfIn("Checker Operations", res)
|
||||
self.failIfIn("Mutable File Operations", res)
|
||||
self.failIfIn("Directory Operations", res)
|
||||
d.addCallback(_check_info, expect_readcap=False)
|
||||
d.addCallback(lambda ign: self.GET(self.rooturl+"future?t=info"))
|
||||
d.addCallback(_check_info, expect_readcap=True)
|
||||
|
||||
# FIXME: these should have expect_rw_uri=not immutable; I don't know
|
||||
# why they fail. Possibly related to ticket #922.
|
||||
|
||||
d.addCallback(lambda ign: self.GET(expected_info_url))
|
||||
d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
|
||||
d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
|
||||
d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
|
||||
|
||||
def _check_json(res, expect_rw_uri):
|
||||
data = simplejson.loads(res)
|
||||
self.failUnlessEqual(data[0], "unknown")
|
||||
if expect_rw_uri:
|
||||
self.failUnlessEqual(data[1]["rw_uri"], future_write_uri)
|
||||
else:
|
||||
self.failIfIn("rw_uri", data[1])
|
||||
self.failUnlessEqual(data[1]["ro_uri"],
|
||||
("imm." if immutable else "ro.") + future_read_uri)
|
||||
# TODO: check metadata contents
|
||||
self.failUnless("metadata" in data[1])
|
||||
|
||||
d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
|
||||
d.addCallback(_check_json, expect_rw_uri=not immutable)
|
||||
|
||||
# and make sure that a read-only version of the directory can be
|
||||
# rendered too. This version will not have future_writecap
|
||||
# rendered too. This version will not have future_write_uri, whether
|
||||
# or not future_node was immutable.
|
||||
d.addCallback(lambda ign: self.GET(self.rourl))
|
||||
d.addCallback(_check_html)
|
||||
d.addCallback(_check_directory_html)
|
||||
d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
|
||||
d.addCallback(_check_json, expect_writecap=False)
|
||||
d.addCallback(_check_directory_json, expect_rw_uri=False)
|
||||
|
||||
d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
|
||||
d.addCallback(_check_json, expect_rw_uri=False)
|
||||
|
||||
# TODO: check that getting t=info from the Info link in the ro directory
|
||||
# works, and does not include the writecap URI.
|
||||
return d
|
||||
|
||||
def test_immutable_unknown(self):
|
||||
return self.test_unknown(immutable=True)
|
||||
|
||||
def test_mutant_dirnodes_are_omitted(self):
|
||||
self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
|
||||
|
||||
self.set_up_grid()
|
||||
c = self.g.clients[0]
|
||||
nm = c.nodemaker
|
||||
self.uris = {}
|
||||
self.fileurls = {}
|
||||
|
||||
lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
|
||||
mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
|
||||
mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
|
||||
|
||||
# This method tests mainly dirnode, but we'd have to duplicate code in order to
|
||||
# test the dirnode and web layers separately.
|
||||
|
||||
# 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
|
||||
# and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
|
||||
# When the directory is read, the mutants should be silently disposed of, leaving
|
||||
# their lonely sibling.
|
||||
# We don't test the case of a retrieving a cap from the encrypted rw_uri field,
|
||||
# because immutable directories don't have a writecap and therefore that field
|
||||
# isn't (and can't be) decrypted.
|
||||
# TODO: The field still exists in the netstring. Technically we should check what
|
||||
# happens if something is put there (it should be ignored), but that can wait.
|
||||
|
||||
lonely_child = nm.create_from_cap(lonely_uri)
|
||||
mutant_ro_child = nm.create_from_cap(mut_read_uri)
|
||||
mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
|
||||
|
||||
def _by_hook_or_by_crook():
|
||||
return True
|
||||
for n in [mutant_ro_child, mutant_write_in_ro_child]:
|
||||
n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
|
||||
|
||||
mutant_write_in_ro_child.get_write_uri = lambda: None
|
||||
mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
|
||||
|
||||
kids = {u"lonely": (lonely_child, {}),
|
||||
u"ro": (mutant_ro_child, {}),
|
||||
u"write-in-ro": (mutant_write_in_ro_child, {}),
|
||||
}
|
||||
d = c.create_immutable_dirnode(kids)
|
||||
|
||||
def _created(dn):
|
||||
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
|
||||
self.failIf(dn.is_mutable())
|
||||
self.failUnless(dn.is_readonly())
|
||||
# This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
|
||||
self.failIf(hasattr(dn._node, 'get_writekey'))
|
||||
rep = str(dn)
|
||||
self.failUnless("RO-IMM" in rep)
|
||||
cap = dn.get_cap()
|
||||
self.failUnlessIn("CHK", cap.to_string())
|
||||
self.cap = cap
|
||||
self.rootnode = dn
|
||||
self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
|
||||
return download_to_data(dn._node)
|
||||
d.addCallback(_created)
|
||||
|
||||
def _check_data(data):
|
||||
# Decode the netstring representation of the directory to check that all children
|
||||
# are present. This is a bit of an abstraction violation, but there's not really
|
||||
# any other way to do it given that the real DirectoryNode._unpack_contents would
|
||||
# strip the mutant children out (which is what we're trying to test, later).
|
||||
position = 0
|
||||
numkids = 0
|
||||
while position < len(data):
|
||||
entries, position = split_netstring(data, 1, position)
|
||||
entry = entries[0]
|
||||
(name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
|
||||
name = name.decode("utf-8")
|
||||
self.failUnless(rwcapdata == "")
|
||||
ro_uri = ro_uri.strip()
|
||||
if name in kids:
|
||||
self.failIfEqual(ro_uri, "")
|
||||
(expected_child, ign) = kids[name]
|
||||
self.failUnlessEqual(ro_uri, expected_child.get_readonly_uri())
|
||||
numkids += 1
|
||||
|
||||
self.failUnlessEqual(numkids, 3)
|
||||
return self.rootnode.list()
|
||||
d.addCallback(_check_data)
|
||||
|
||||
# Now when we use the real directory listing code, the mutants should be absent.
|
||||
def _check_kids(children):
|
||||
self.failUnlessEqual(sorted(children.keys()), [u"lonely"])
|
||||
lonely_node, lonely_metadata = children[u"lonely"]
|
||||
|
||||
self.failUnlessEqual(lonely_node.get_write_uri(), None)
|
||||
self.failUnlessEqual(lonely_node.get_readonly_uri(), lonely_uri)
|
||||
d.addCallback(_check_kids)
|
||||
|
||||
d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
|
||||
d.addCallback(lambda n: n.list())
|
||||
d.addCallback(_check_kids) # again with dirnode recreated from cap
|
||||
|
||||
# Make sure the lonely child can be listed in HTML...
|
||||
d.addCallback(lambda ign: self.GET(self.rooturl))
|
||||
def _check_html(res):
|
||||
self.failIfIn("URI:SSK", res)
|
||||
get_lonely = "".join([r'<td>FILE</td>',
|
||||
r'\s+<td>',
|
||||
r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
|
||||
r'</td>',
|
||||
r'\s+<td>%d</td>' % len("one"),
|
||||
])
|
||||
self.failUnless(re.search(get_lonely, res), res)
|
||||
|
||||
# find the More Info link for name, should be relative
|
||||
mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
|
||||
info_url = mo.group(1)
|
||||
self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
|
||||
d.addCallback(_check_html)
|
||||
|
||||
# ... and in JSON.
|
||||
d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
|
||||
def _check_json(res):
|
||||
data = simplejson.loads(res)
|
||||
self.failUnlessEqual(data[0], "dirnode")
|
||||
listed_children = data[1]["children"]
|
||||
self.failUnlessEqual(sorted(listed_children.keys()), [u"lonely"])
|
||||
ll_type, ll_data = listed_children[u"lonely"]
|
||||
self.failUnlessEqual(ll_type, "filenode")
|
||||
self.failIf("rw_uri" in ll_data)
|
||||
self.failUnlessEqual(ll_data["ro_uri"], lonely_uri)
|
||||
d.addCallback(_check_json)
|
||||
return d
|
||||
|
||||
def test_deep_check(self):
|
||||
@ -3178,10 +3440,10 @@ class Grid(GridTestMixin, WebErrorMixin, unittest.TestCase, ShouldFailMixin):
|
||||
|
||||
# this tests that deep-check and stream-manifest will ignore
|
||||
# UnknownNode instances. Hopefully this will also cover deep-stats.
|
||||
future_writecap = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
future_node = UnknownNode(future_writecap, future_readcap)
|
||||
d.addCallback(lambda ign: self.rootnode.set_node(u"future",future_node))
|
||||
future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
future_node = UnknownNode(future_write_uri, future_read_uri)
|
||||
d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
|
||||
|
||||
def _clobber_shares(ignored):
|
||||
self.delete_shares_numbered(self.uris["sick"], [0,1])
|
||||
|
@ -1,29 +1,181 @@
|
||||
|
||||
from zope.interface import implements
|
||||
from twisted.internet import defer
|
||||
from allmydata.interfaces import IFilesystemNode
|
||||
from allmydata.interfaces import IFilesystemNode, MustNotBeUnknownRWError, \
|
||||
MustBeDeepImmutableError
|
||||
from allmydata import uri
|
||||
from allmydata.uri import ALLEGED_READONLY_PREFIX, ALLEGED_IMMUTABLE_PREFIX
|
||||
|
||||
|
||||
# See ticket #833 for design rationale of UnknownNodes.
|
||||
|
||||
def strip_prefix_for_ro(ro_uri, deep_immutable):
|
||||
"""Strip prefixes when storing an URI in a ro_uri slot."""
|
||||
|
||||
# It is possible for an alleged-immutable URI to be put into a
|
||||
# mutable directory. In that case the ALLEGED_IMMUTABLE_PREFIX
|
||||
# should not be stripped. In other cases, the prefix can safely
|
||||
# be stripped because it is implied by the context.
|
||||
|
||||
if ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX):
|
||||
if not deep_immutable:
|
||||
return ro_uri
|
||||
return ro_uri[len(ALLEGED_IMMUTABLE_PREFIX):]
|
||||
elif ro_uri.startswith(ALLEGED_READONLY_PREFIX):
|
||||
return ro_uri[len(ALLEGED_READONLY_PREFIX):]
|
||||
else:
|
||||
return ro_uri
|
||||
|
||||
class UnknownNode:
|
||||
implements(IFilesystemNode)
|
||||
def __init__(self, writecap, readcap):
|
||||
assert writecap is None or isinstance(writecap, str)
|
||||
self.writecap = writecap
|
||||
assert readcap is None or isinstance(readcap, str)
|
||||
self.readcap = readcap
|
||||
|
||||
def __init__(self, given_rw_uri, given_ro_uri, deep_immutable=False,
|
||||
name=u"<unknown name>"):
|
||||
assert given_rw_uri is None or isinstance(given_rw_uri, str)
|
||||
assert given_ro_uri is None or isinstance(given_ro_uri, str)
|
||||
given_rw_uri = given_rw_uri or None
|
||||
given_ro_uri = given_ro_uri or None
|
||||
|
||||
# We don't raise errors when creating an UnknownNode; we instead create an
|
||||
# opaque node (with rw_uri and ro_uri both None) that records the error.
|
||||
# This avoids breaking operations that never store the opaque node.
|
||||
# Note that this means that if a stored dirnode has only a rw_uri, it
|
||||
# might be dropped. Any future "write-only" cap formats should have a dummy
|
||||
# unusable readcap to stop that from happening.
|
||||
|
||||
self.error = None
|
||||
self.rw_uri = self.ro_uri = None
|
||||
if given_rw_uri:
|
||||
if deep_immutable:
|
||||
if given_rw_uri.startswith(ALLEGED_IMMUTABLE_PREFIX) and not given_ro_uri:
|
||||
# We needed an immutable cap, and were given one. It was given in the
|
||||
# rw_uri slot, but that's fine; we'll move it to ro_uri below.
|
||||
pass
|
||||
elif not given_ro_uri:
|
||||
self.error = MustNotBeUnknownRWError("cannot attach unknown rw cap as immutable child",
|
||||
name, True)
|
||||
return # node will be opaque
|
||||
else:
|
||||
# We could report either error, but this probably makes more sense.
|
||||
self.error = MustBeDeepImmutableError("cannot attach unknown rw cap as immutable child",
|
||||
name)
|
||||
return # node will be opaque
|
||||
|
||||
if not given_ro_uri:
|
||||
# We were given a single cap argument, or a rw_uri with no ro_uri.
|
||||
|
||||
if not (given_rw_uri.startswith(ALLEGED_READONLY_PREFIX)
|
||||
or given_rw_uri.startswith(ALLEGED_IMMUTABLE_PREFIX)):
|
||||
# If the single cap is unprefixed, then we cannot tell whether it is a
|
||||
# writecap, and we don't know how to diminish it to a readcap if it is one.
|
||||
# If it didn't *already* have at least an ALLEGED_READONLY_PREFIX, then
|
||||
# prefixing it would be a bad idea because we have been given no reason
|
||||
# to believe that it is a readcap, so we might be letting a client
|
||||
# inadvertently grant excess write authority.
|
||||
self.error = MustNotBeUnknownRWError("cannot attach unknown rw cap as child",
|
||||
name, False)
|
||||
return # node will be opaque
|
||||
|
||||
# OTOH, if the single cap already had a prefix (which is of the required
|
||||
# strength otherwise an error would have been thrown above), then treat it
|
||||
# as though it had been given in the ro_uri slot. This has a similar effect
|
||||
# to the use for known caps of 'bigcap = writecap or readcap' in
|
||||
# nodemaker.py: create_from_cap. It enables copying of unknown readcaps to
|
||||
# work in as many cases as we can securely allow.
|
||||
given_ro_uri = given_rw_uri
|
||||
given_rw_uri = None
|
||||
elif given_ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX):
|
||||
# Strange corner case: we were given a cap in both slots, with the ro_uri
|
||||
# alleged to be immutable. A real immutable object wouldn't have a writecap.
|
||||
self.error = MustBeDeepImmutableError("cannot accept a child entry that specifies "
|
||||
"both rw_uri, and ro_uri with an imm. prefix",
|
||||
name)
|
||||
return # node will be opaque
|
||||
|
||||
# If the ro_uri definitely fails the constraint, it should be treated as opaque and
|
||||
# the error recorded.
|
||||
if given_ro_uri:
|
||||
read_cap = uri.from_string(given_ro_uri, deep_immutable=deep_immutable, name=name)
|
||||
if isinstance(read_cap, uri.UnknownURI):
|
||||
self.error = read_cap.get_error()
|
||||
if self.error:
|
||||
assert self.rw_uri is None and self.ro_uri is None
|
||||
return
|
||||
|
||||
if deep_immutable:
|
||||
assert self.rw_uri is None
|
||||
# strengthen the constraint on ro_uri to ALLEGED_IMMUTABLE_PREFIX
|
||||
if given_ro_uri:
|
||||
if given_ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX):
|
||||
self.ro_uri = given_ro_uri
|
||||
elif given_ro_uri.startswith(ALLEGED_READONLY_PREFIX):
|
||||
self.ro_uri = ALLEGED_IMMUTABLE_PREFIX + given_ro_uri[len(ALLEGED_READONLY_PREFIX):]
|
||||
else:
|
||||
self.ro_uri = ALLEGED_IMMUTABLE_PREFIX + given_ro_uri
|
||||
else:
|
||||
# not immutable, so a writecap is allowed
|
||||
self.rw_uri = given_rw_uri
|
||||
# strengthen the constraint on ro_uri to ALLEGED_READONLY_PREFIX
|
||||
if given_ro_uri:
|
||||
if (given_ro_uri.startswith(ALLEGED_READONLY_PREFIX) or
|
||||
given_ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX)):
|
||||
self.ro_uri = given_ro_uri
|
||||
else:
|
||||
self.ro_uri = ALLEGED_READONLY_PREFIX + given_ro_uri
|
||||
|
||||
def get_cap(self):
|
||||
return uri.UnknownURI(self.rw_uri or self.ro_uri)
|
||||
|
||||
def get_readcap(self):
|
||||
return uri.UnknownURI(self.ro_uri)
|
||||
|
||||
def is_readonly(self):
|
||||
raise AssertionError("an UnknownNode might be either read-only or "
|
||||
"read/write, so we shouldn't be calling is_readonly")
|
||||
|
||||
def is_mutable(self):
|
||||
raise AssertionError("an UnknownNode might be either mutable or immutable, "
|
||||
"so we shouldn't be calling is_mutable")
|
||||
|
||||
def is_unknown(self):
|
||||
return True
|
||||
|
||||
def is_allowed_in_immutable_directory(self):
|
||||
# An UnknownNode consisting only of a ro_uri is allowed in an
|
||||
# immutable directory, even though we do not know that it is
|
||||
# immutable (or even read-only), provided that no error was detected.
|
||||
return not self.error and not self.rw_uri
|
||||
|
||||
def raise_error(self):
|
||||
if self.error is not None:
|
||||
raise self.error
|
||||
|
||||
def get_uri(self):
|
||||
return self.writecap
|
||||
return self.rw_uri or self.ro_uri
|
||||
|
||||
def get_write_uri(self):
|
||||
return self.rw_uri
|
||||
|
||||
def get_readonly_uri(self):
|
||||
return self.readcap
|
||||
return self.ro_uri
|
||||
|
||||
def get_storage_index(self):
|
||||
return None
|
||||
|
||||
def get_verify_cap(self):
|
||||
return None
|
||||
|
||||
def get_repair_cap(self):
|
||||
return None
|
||||
|
||||
def get_size(self):
|
||||
return None
|
||||
|
||||
def get_current_size(self):
|
||||
return defer.succeed(None)
|
||||
|
||||
def check(self, monitor, verify, add_lease):
|
||||
return defer.succeed(None)
|
||||
|
||||
def check_and_repair(self, monitor, verify, add_lease):
|
||||
return defer.succeed(None)
|
||||
|
@ -5,9 +5,10 @@ from twisted.python.components import registerAdapter
|
||||
from allmydata.storage.server import si_a2b, si_b2a
|
||||
from allmydata.util import base32, hashutil
|
||||
from allmydata.interfaces import IURI, IDirnodeURI, IFileURI, IImmutableFileURI, \
|
||||
IVerifierURI, IMutableFileURI, IDirectoryURI, IReadonlyDirectoryURI
|
||||
IVerifierURI, IMutableFileURI, IDirectoryURI, IReadonlyDirectoryURI, \
|
||||
MustBeDeepImmutableError, MustBeReadonlyError, CapConstraintError
|
||||
|
||||
class BadURIError(Exception):
|
||||
class BadURIError(CapConstraintError):
|
||||
pass
|
||||
|
||||
# the URI shall be an ascii representation of the file. It shall contain
|
||||
@ -71,7 +72,7 @@ class CHKFileURI(_BaseURI):
|
||||
def init_from_human_encoding(cls, uri):
|
||||
mo = cls.HUMAN_RE.search(uri)
|
||||
if not mo:
|
||||
raise BadURIError("%s doesn't look like a cap" % (uri,))
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
|
||||
int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
|
||||
|
||||
@ -79,7 +80,7 @@ class CHKFileURI(_BaseURI):
|
||||
def init_from_string(cls, uri):
|
||||
mo = cls.STRING_RE.search(uri)
|
||||
if not mo:
|
||||
raise BadURIError("%s doesn't look like a cap" % (uri,))
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
|
||||
int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
|
||||
|
||||
@ -97,8 +98,10 @@ class CHKFileURI(_BaseURI):
|
||||
|
||||
def is_readonly(self):
|
||||
return True
|
||||
|
||||
def is_mutable(self):
|
||||
return False
|
||||
|
||||
def get_readonly(self):
|
||||
return self
|
||||
|
||||
@ -134,14 +137,16 @@ class CHKFileVerifierURI(_BaseURI):
|
||||
@classmethod
|
||||
def init_from_human_encoding(cls, uri):
|
||||
mo = cls.HUMAN_RE.search(uri)
|
||||
assert mo, uri
|
||||
if not mo:
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
|
||||
int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
|
||||
|
||||
@classmethod
|
||||
def init_from_string(cls, uri):
|
||||
mo = cls.STRING_RE.search(uri)
|
||||
assert mo, (uri, cls, cls.STRING_RE)
|
||||
if not mo:
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)),
|
||||
int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
|
||||
|
||||
@ -157,6 +162,18 @@ class CHKFileVerifierURI(_BaseURI):
|
||||
self.total_shares,
|
||||
self.size))
|
||||
|
||||
def is_readonly(self):
|
||||
return True
|
||||
|
||||
def is_mutable(self):
|
||||
return False
|
||||
|
||||
def get_readonly(self):
|
||||
return self
|
||||
|
||||
def get_verify_cap(self):
|
||||
return self
|
||||
|
||||
|
||||
class LiteralFileURI(_BaseURI):
|
||||
implements(IURI, IImmutableFileURI)
|
||||
@ -173,13 +190,15 @@ class LiteralFileURI(_BaseURI):
|
||||
@classmethod
|
||||
def init_from_human_encoding(cls, uri):
|
||||
mo = cls.HUMAN_RE.search(uri)
|
||||
assert mo, uri
|
||||
if not mo:
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(base32.a2b(mo.group(1)))
|
||||
|
||||
@classmethod
|
||||
def init_from_string(cls, uri):
|
||||
mo = cls.STRING_RE.search(uri)
|
||||
assert mo, uri
|
||||
if not mo:
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(base32.a2b(mo.group(1)))
|
||||
|
||||
def to_string(self):
|
||||
@ -187,10 +206,13 @@ class LiteralFileURI(_BaseURI):
|
||||
|
||||
def is_readonly(self):
|
||||
return True
|
||||
|
||||
def is_mutable(self):
|
||||
return False
|
||||
|
||||
def get_readonly(self):
|
||||
return self
|
||||
|
||||
def get_storage_index(self):
|
||||
return None
|
||||
|
||||
@ -201,6 +223,7 @@ class LiteralFileURI(_BaseURI):
|
||||
def get_size(self):
|
||||
return len(self.data)
|
||||
|
||||
|
||||
class WriteableSSKFileURI(_BaseURI):
|
||||
implements(IURI, IMutableFileURI)
|
||||
|
||||
@ -221,7 +244,7 @@ class WriteableSSKFileURI(_BaseURI):
|
||||
def init_from_human_encoding(cls, uri):
|
||||
mo = cls.HUMAN_RE.search(uri)
|
||||
if not mo:
|
||||
raise BadURIError("'%s' doesn't look like a cap" % (uri,))
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
|
||||
|
||||
@classmethod
|
||||
@ -242,18 +265,23 @@ class WriteableSSKFileURI(_BaseURI):
|
||||
|
||||
def abbrev(self):
|
||||
return base32.b2a(self.writekey[:5])
|
||||
|
||||
def abbrev_si(self):
|
||||
return base32.b2a(self.storage_index)[:5]
|
||||
|
||||
def is_readonly(self):
|
||||
return False
|
||||
|
||||
def is_mutable(self):
|
||||
return True
|
||||
|
||||
def get_readonly(self):
|
||||
return ReadonlySSKFileURI(self.readkey, self.fingerprint)
|
||||
|
||||
def get_verify_cap(self):
|
||||
return SSKVerifierURI(self.storage_index, self.fingerprint)
|
||||
|
||||
|
||||
class ReadonlySSKFileURI(_BaseURI):
|
||||
implements(IURI, IMutableFileURI)
|
||||
|
||||
@ -271,14 +299,14 @@ class ReadonlySSKFileURI(_BaseURI):
|
||||
def init_from_human_encoding(cls, uri):
|
||||
mo = cls.HUMAN_RE.search(uri)
|
||||
if not mo:
|
||||
raise BadURIError("'%s' doesn't look like a cap" % (uri,))
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
|
||||
|
||||
@classmethod
|
||||
def init_from_string(cls, uri):
|
||||
mo = cls.STRING_RE.search(uri)
|
||||
if not mo:
|
||||
raise BadURIError("'%s' doesn't look like a cap" % (uri,))
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
|
||||
|
||||
def to_string(self):
|
||||
@ -292,18 +320,23 @@ class ReadonlySSKFileURI(_BaseURI):
|
||||
|
||||
def abbrev(self):
|
||||
return base32.b2a(self.readkey[:5])
|
||||
|
||||
def abbrev_si(self):
|
||||
return base32.b2a(self.storage_index)[:5]
|
||||
|
||||
def is_readonly(self):
|
||||
return True
|
||||
|
||||
def is_mutable(self):
|
||||
return True
|
||||
|
||||
def get_readonly(self):
|
||||
return self
|
||||
|
||||
def get_verify_cap(self):
|
||||
return SSKVerifierURI(self.storage_index, self.fingerprint)
|
||||
|
||||
|
||||
class SSKVerifierURI(_BaseURI):
|
||||
implements(IVerifierURI)
|
||||
|
||||
@ -319,13 +352,15 @@ class SSKVerifierURI(_BaseURI):
|
||||
@classmethod
|
||||
def init_from_human_encoding(cls, uri):
|
||||
mo = cls.HUMAN_RE.search(uri)
|
||||
assert mo, uri
|
||||
if not mo:
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
|
||||
|
||||
@classmethod
|
||||
def init_from_string(cls, uri):
|
||||
mo = cls.STRING_RE.search(uri)
|
||||
assert mo, (uri, cls)
|
||||
if not mo:
|
||||
raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
|
||||
return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
|
||||
|
||||
def to_string(self):
|
||||
@ -334,6 +369,18 @@ class SSKVerifierURI(_BaseURI):
|
||||
return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index),
|
||||
base32.b2a(self.fingerprint))
|
||||
|
||||
def is_readonly(self):
|
||||
return True
|
||||
|
||||
def is_mutable(self):
|
||||
return False
|
||||
|
||||
def get_readonly(self):
|
||||
return self
|
||||
|
||||
def get_verify_cap(self):
|
||||
return self
|
||||
|
||||
class _DirectoryBaseURI(_BaseURI):
|
||||
implements(IURI, IDirnodeURI)
|
||||
def __init__(self, filenode_uri=None):
|
||||
@ -373,15 +420,16 @@ class _DirectoryBaseURI(_BaseURI):
|
||||
|
||||
def abbrev(self):
|
||||
return self._filenode_uri.to_string().split(':')[2][:5]
|
||||
|
||||
def abbrev_si(self):
|
||||
return base32.b2a(self._filenode_uri.storage_index)[:5]
|
||||
|
||||
def get_filenode_cap(self):
|
||||
return self._filenode_uri
|
||||
|
||||
def is_mutable(self):
|
||||
return True
|
||||
|
||||
def get_filenode_cap(self):
|
||||
return self._filenode_uri
|
||||
|
||||
def get_verify_cap(self):
|
||||
return DirectoryURIVerifier(self._filenode_uri.get_verify_cap())
|
||||
|
||||
@ -407,6 +455,7 @@ class DirectoryURI(_DirectoryBaseURI):
|
||||
def get_readonly(self):
|
||||
return ReadonlyDirectoryURI(self._filenode_uri.get_readonly())
|
||||
|
||||
|
||||
class ReadonlyDirectoryURI(_DirectoryBaseURI):
|
||||
implements(IReadonlyDirectoryURI)
|
||||
|
||||
@ -426,26 +475,30 @@ class ReadonlyDirectoryURI(_DirectoryBaseURI):
|
||||
def get_readonly(self):
|
||||
return self
|
||||
|
||||
|
||||
class _ImmutableDirectoryBaseURI(_DirectoryBaseURI):
|
||||
def __init__(self, filenode_uri=None):
|
||||
if filenode_uri:
|
||||
assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri
|
||||
assert not filenode_uri.is_mutable()
|
||||
_DirectoryBaseURI.__init__(self, filenode_uri)
|
||||
|
||||
def is_mutable(self):
|
||||
return False
|
||||
|
||||
def is_readonly(self):
|
||||
return True
|
||||
|
||||
def is_mutable(self):
|
||||
return False
|
||||
|
||||
def get_readonly(self):
|
||||
return self
|
||||
|
||||
|
||||
class ImmutableDirectoryURI(_ImmutableDirectoryBaseURI):
|
||||
BASE_STRING='URI:DIR2-CHK:'
|
||||
BASE_STRING_RE=re.compile('^'+BASE_STRING)
|
||||
BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK'+SEP)
|
||||
INNER_URI_CLASS=CHKFileURI
|
||||
|
||||
def get_verify_cap(self):
|
||||
vcap = self._filenode_uri.get_verify_cap()
|
||||
return ImmutableDirectoryURIVerifier(vcap)
|
||||
@ -456,10 +509,12 @@ class LiteralDirectoryURI(_ImmutableDirectoryBaseURI):
|
||||
BASE_STRING_RE=re.compile('^'+BASE_STRING)
|
||||
BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-LIT'+SEP)
|
||||
INNER_URI_CLASS=LiteralFileURI
|
||||
|
||||
def get_verify_cap(self):
|
||||
# LIT caps have no verifier, since they aren't distributed
|
||||
return None
|
||||
|
||||
|
||||
def wrap_dirnode_cap(filecap):
|
||||
if isinstance(filecap, WriteableSSKFileURI):
|
||||
return DirectoryURI(filecap)
|
||||
@ -469,7 +524,8 @@ def wrap_dirnode_cap(filecap):
|
||||
return ImmutableDirectoryURI(filecap)
|
||||
if isinstance(filecap, LiteralFileURI):
|
||||
return LiteralDirectoryURI(filecap)
|
||||
assert False, "cannot wrap a dirnode around %s" % filecap.__class__
|
||||
assert False, "cannot interpret as a directory cap: %s" % filecap.__class__
|
||||
|
||||
|
||||
class DirectoryURIVerifier(_DirectoryBaseURI):
|
||||
implements(IVerifierURI)
|
||||
@ -487,6 +543,10 @@ class DirectoryURIVerifier(_DirectoryBaseURI):
|
||||
def get_filenode_cap(self):
|
||||
return self._filenode_uri
|
||||
|
||||
def is_mutable(self):
|
||||
return False
|
||||
|
||||
|
||||
class ImmutableDirectoryURIVerifier(DirectoryURIVerifier):
|
||||
implements(IVerifierURI)
|
||||
BASE_STRING='URI:DIR2-CHK-Verifier:'
|
||||
@ -494,68 +554,146 @@ class ImmutableDirectoryURIVerifier(DirectoryURIVerifier):
|
||||
BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK-VERIFIER'+SEP)
|
||||
INNER_URI_CLASS=CHKFileVerifierURI
|
||||
|
||||
|
||||
class UnknownURI:
|
||||
def __init__(self, uri):
|
||||
def __init__(self, uri, error=None):
|
||||
self._uri = uri
|
||||
self._error = error
|
||||
|
||||
def to_string(self):
|
||||
return self._uri
|
||||
|
||||
def from_string(s):
|
||||
if not isinstance(s, str):
|
||||
raise TypeError("unknown URI type: %s.." % str(s)[:100])
|
||||
elif s.startswith('URI:CHK:'):
|
||||
return CHKFileURI.init_from_string(s)
|
||||
elif s.startswith('URI:CHK-Verifier:'):
|
||||
return CHKFileVerifierURI.init_from_string(s)
|
||||
elif s.startswith('URI:LIT:'):
|
||||
return LiteralFileURI.init_from_string(s)
|
||||
elif s.startswith('URI:SSK:'):
|
||||
return WriteableSSKFileURI.init_from_string(s)
|
||||
elif s.startswith('URI:SSK-RO:'):
|
||||
return ReadonlySSKFileURI.init_from_string(s)
|
||||
elif s.startswith('URI:SSK-Verifier:'):
|
||||
return SSKVerifierURI.init_from_string(s)
|
||||
elif s.startswith('URI:DIR2:'):
|
||||
return DirectoryURI.init_from_string(s)
|
||||
elif s.startswith('URI:DIR2-RO:'):
|
||||
return ReadonlyDirectoryURI.init_from_string(s)
|
||||
elif s.startswith('URI:DIR2-Verifier:'):
|
||||
return DirectoryURIVerifier.init_from_string(s)
|
||||
elif s.startswith('URI:DIR2-CHK:'):
|
||||
return ImmutableDirectoryURI.init_from_string(s)
|
||||
elif s.startswith('URI:DIR2-LIT:'):
|
||||
return LiteralDirectoryURI.init_from_string(s)
|
||||
return UnknownURI(s)
|
||||
def get_readonly(self):
|
||||
return None
|
||||
|
||||
def get_error(self):
|
||||
return self._error
|
||||
|
||||
def get_verify_cap(self):
|
||||
return None
|
||||
|
||||
|
||||
ALLEGED_READONLY_PREFIX = 'ro.'
|
||||
ALLEGED_IMMUTABLE_PREFIX = 'imm.'
|
||||
|
||||
def from_string(u, deep_immutable=False, name=u"<unknown name>"):
|
||||
if not isinstance(u, str):
|
||||
raise TypeError("unknown URI type: %s.." % str(u)[:100])
|
||||
|
||||
# We allow and check ALLEGED_READONLY_PREFIX or ALLEGED_IMMUTABLE_PREFIX
|
||||
# on all URIs, even though we would only strictly need to do so for caps of
|
||||
# new formats (post Tahoe-LAFS 1.6). URIs that are not consistent with their
|
||||
# prefix are treated as unknown. This should be revisited when we add the
|
||||
# new cap formats. See <http://allmydata.org/trac/tahoe/ticket/833#comment:31>.
|
||||
s = u
|
||||
can_be_mutable = can_be_writeable = not deep_immutable
|
||||
if s.startswith(ALLEGED_IMMUTABLE_PREFIX):
|
||||
can_be_mutable = can_be_writeable = False
|
||||
s = s[len(ALLEGED_IMMUTABLE_PREFIX):]
|
||||
elif s.startswith(ALLEGED_READONLY_PREFIX):
|
||||
can_be_writeable = False
|
||||
s = s[len(ALLEGED_READONLY_PREFIX):]
|
||||
|
||||
error = None
|
||||
kind = "cap"
|
||||
try:
|
||||
if s.startswith('URI:CHK:'):
|
||||
return CHKFileURI.init_from_string(s)
|
||||
elif s.startswith('URI:CHK-Verifier:'):
|
||||
return CHKFileVerifierURI.init_from_string(s)
|
||||
elif s.startswith('URI:LIT:'):
|
||||
return LiteralFileURI.init_from_string(s)
|
||||
elif s.startswith('URI:SSK:'):
|
||||
if can_be_writeable:
|
||||
return WriteableSSKFileURI.init_from_string(s)
|
||||
kind = "URI:SSK file writecap"
|
||||
elif s.startswith('URI:SSK-RO:'):
|
||||
if can_be_mutable:
|
||||
return ReadonlySSKFileURI.init_from_string(s)
|
||||
kind = "URI:SSK-RO readcap to a mutable file"
|
||||
elif s.startswith('URI:SSK-Verifier:'):
|
||||
return SSKVerifierURI.init_from_string(s)
|
||||
elif s.startswith('URI:DIR2:'):
|
||||
if can_be_writeable:
|
||||
return DirectoryURI.init_from_string(s)
|
||||
kind = "URI:DIR2 directory writecap"
|
||||
elif s.startswith('URI:DIR2-RO:'):
|
||||
if can_be_mutable:
|
||||
return ReadonlyDirectoryURI.init_from_string(s)
|
||||
kind = "URI:DIR2-RO readcap to a mutable directory"
|
||||
elif s.startswith('URI:DIR2-Verifier:'):
|
||||
return DirectoryURIVerifier.init_from_string(s)
|
||||
elif s.startswith('URI:DIR2-CHK:'):
|
||||
return ImmutableDirectoryURI.init_from_string(s)
|
||||
elif s.startswith('URI:DIR2-LIT:'):
|
||||
return LiteralDirectoryURI.init_from_string(s)
|
||||
elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable:
|
||||
# For testing how future writeable caps would behave in read-only contexts.
|
||||
kind = "x-tahoe-future-test-writeable: testing cap"
|
||||
elif s.startswith('x-tahoe-future-test-mutable:') and not can_be_mutable:
|
||||
# For testing how future mutable readcaps would behave in immutable contexts.
|
||||
kind = "x-tahoe-future-test-mutable: testing cap"
|
||||
else:
|
||||
return UnknownURI(u)
|
||||
|
||||
# We fell through because a constraint was not met.
|
||||
# Prefer to report the most specific constraint.
|
||||
if not can_be_mutable:
|
||||
error = MustBeDeepImmutableError(kind + " used in an immutable context", name)
|
||||
else:
|
||||
error = MustBeReadonlyError(kind + " used in a read-only context", name)
|
||||
|
||||
except BadURIError, e:
|
||||
error = e
|
||||
|
||||
return UnknownURI(u, error=error)
|
||||
|
||||
def is_uri(s):
|
||||
try:
|
||||
from_string(s)
|
||||
from_string(s, deep_immutable=False)
|
||||
return True
|
||||
except (TypeError, AssertionError):
|
||||
return False
|
||||
|
||||
def from_string_dirnode(s):
|
||||
u = from_string(s)
|
||||
def is_literal_file_uri(s):
|
||||
if not isinstance(s, str):
|
||||
return False
|
||||
return (s.startswith('URI:LIT:') or
|
||||
s.startswith(ALLEGED_READONLY_PREFIX + 'URI:LIT:') or
|
||||
s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:LIT:'))
|
||||
|
||||
def has_uri_prefix(s):
|
||||
if not isinstance(s, str):
|
||||
return False
|
||||
return (s.startswith("URI:") or
|
||||
s.startswith(ALLEGED_READONLY_PREFIX + 'URI:') or
|
||||
s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:'))
|
||||
|
||||
|
||||
# These take the same keyword arguments as from_string above.
|
||||
|
||||
def from_string_dirnode(s, **kwargs):
|
||||
u = from_string(s, **kwargs)
|
||||
assert IDirnodeURI.providedBy(u)
|
||||
return u
|
||||
|
||||
registerAdapter(from_string_dirnode, str, IDirnodeURI)
|
||||
|
||||
def from_string_filenode(s):
|
||||
u = from_string(s)
|
||||
def from_string_filenode(s, **kwargs):
|
||||
u = from_string(s, **kwargs)
|
||||
assert IFileURI.providedBy(u)
|
||||
return u
|
||||
|
||||
registerAdapter(from_string_filenode, str, IFileURI)
|
||||
|
||||
def from_string_mutable_filenode(s):
|
||||
u = from_string(s)
|
||||
def from_string_mutable_filenode(s, **kwargs):
|
||||
u = from_string(s, **kwargs)
|
||||
assert IMutableFileURI.providedBy(u)
|
||||
return u
|
||||
registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
|
||||
|
||||
def from_string_verifier(s):
|
||||
u = from_string(s)
|
||||
def from_string_verifier(s, **kwargs):
|
||||
u = from_string(s, **kwargs)
|
||||
assert IVerifierURI.providedBy(u)
|
||||
return u
|
||||
registerAdapter(from_string_verifier, str, IVerifierURI)
|
||||
|
@ -8,7 +8,8 @@ from nevow.inevow import IRequest
|
||||
from nevow.util import resource_filename
|
||||
from allmydata.interfaces import ExistingChildError, NoSuchChildError, \
|
||||
FileTooLargeError, NotEnoughSharesError, NoSharesError, \
|
||||
NotDeepImmutableError, EmptyPathnameComponentError
|
||||
EmptyPathnameComponentError, MustBeDeepImmutableError, \
|
||||
MustBeReadonlyError, MustNotBeUnknownRWError
|
||||
from allmydata.mutable.common import UnrecoverableFileError
|
||||
from allmydata.util import abbreviate # TODO: consolidate
|
||||
|
||||
@ -181,9 +182,42 @@ def humanize_failure(f):
|
||||
"failure, or disk corruption. You should perform a filecheck on "
|
||||
"this object to learn more.")
|
||||
return (t, http.GONE)
|
||||
if f.check(NotDeepImmutableError):
|
||||
t = ("NotDeepImmutableError: a mkdir-immutable operation was given "
|
||||
"a child that was not itself immutable: %s" % (f.value,))
|
||||
if f.check(MustNotBeUnknownRWError):
|
||||
name = f.value.args[1]
|
||||
immutable = f.value.args[2]
|
||||
if immutable:
|
||||
t = ("MustNotBeUnknownRWError: an operation to add a child named "
|
||||
"'%s' to a directory was given an unknown cap in a write slot.\n"
|
||||
"If the cap is actually an immutable readcap, then using a "
|
||||
"webapi server that supports a later version of Tahoe may help.\n\n"
|
||||
"If you are using the webapi directly, then specifying an immutable "
|
||||
"readcap in the read slot (ro_uri) of the JSON PROPDICT, and "
|
||||
"omitting the write slot (rw_uri), would also work in this "
|
||||
"case.") % name.encode("utf-8")
|
||||
else:
|
||||
t = ("MustNotBeUnknownRWError: an operation to add a child named "
|
||||
"'%s' to a directory was given an unknown cap in a write slot.\n"
|
||||
"Using a webapi server that supports a later version of Tahoe "
|
||||
"may help.\n\n"
|
||||
"If you are using the webapi directly, specifying a readcap in "
|
||||
"the read slot (ro_uri) of the JSON PROPDICT, as well as a "
|
||||
"writecap in the write slot if desired, would also work in this "
|
||||
"case.") % name.encode("utf-8")
|
||||
return (t, http.BAD_REQUEST)
|
||||
if f.check(MustBeDeepImmutableError):
|
||||
name = f.value.args[1]
|
||||
t = ("MustBeDeepImmutableError: a cap passed to this operation for "
|
||||
"the child named '%s', needed to be immutable but was not. Either "
|
||||
"the cap is being added to an immutable directory, or it was "
|
||||
"originally retrieved from an immutable directory as an unknown "
|
||||
"cap." % name.encode("utf-8"))
|
||||
return (t, http.BAD_REQUEST)
|
||||
if f.check(MustBeReadonlyError):
|
||||
name = f.value.args[1]
|
||||
t = ("MustBeReadonlyError: a cap passed to this operation for "
|
||||
"the child named '%s', needed to be read-only but was not. "
|
||||
"The cap is being passed in a read slot (ro_uri), or was retrieved "
|
||||
"from a read slot as an unknown cap." % name.encode("utf-8"))
|
||||
return (t, http.BAD_REQUEST)
|
||||
if f.check(WebError):
|
||||
return (f.value.text, f.value.code)
|
||||
|
@ -352,7 +352,12 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
charset = get_arg(req, "_charset", "utf-8")
|
||||
name = name.decode(charset)
|
||||
replace = boolean_of_arg(get_arg(req, "replace", "true"))
|
||||
d = self.node.set_uri(name, childcap, childcap, overwrite=replace)
|
||||
|
||||
# We mustn't pass childcap for the readcap argument because we don't
|
||||
# know whether it is a read cap. Passing a read cap as the writecap
|
||||
# argument will work (it ends up calling NodeMaker.create_from_cap,
|
||||
# which derives a readcap if necessary and possible).
|
||||
d = self.node.set_uri(name, childcap, None, overwrite=replace)
|
||||
d.addCallback(lambda res: childcap)
|
||||
return d
|
||||
|
||||
@ -363,9 +368,9 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
# won't show up in the resulting encoded form.. the 'name'
|
||||
# field is completely missing. So to allow deletion of an
|
||||
# empty file, we have to pretend that None means ''. The only
|
||||
# downide of this is a slightly confusing error message if
|
||||
# downside of this is a slightly confusing error message if
|
||||
# someone does a POST without a name= field. For our own HTML
|
||||
# thisn't a big deal, because we create the 'delete' POST
|
||||
# this isn't a big deal, because we create the 'delete' POST
|
||||
# buttons ourselves.
|
||||
name = ''
|
||||
charset = get_arg(req, "_charset", "utf-8")
|
||||
@ -585,7 +590,11 @@ class DirectoryAsHTML(rend.Page):
|
||||
def render_title(self, ctx, data):
|
||||
si_s = abbreviated_dirnode(self.node)
|
||||
header = ["Tahoe-LAFS - Directory SI=%s" % si_s]
|
||||
if self.node.is_readonly():
|
||||
if self.node.is_unknown():
|
||||
header.append(" (unknown)")
|
||||
elif not self.node.is_mutable():
|
||||
header.append(" (immutable)")
|
||||
elif self.node.is_readonly():
|
||||
header.append(" (read-only)")
|
||||
else:
|
||||
header.append(" (modifiable)")
|
||||
@ -594,7 +603,11 @@ class DirectoryAsHTML(rend.Page):
|
||||
def render_header(self, ctx, data):
|
||||
si_s = abbreviated_dirnode(self.node)
|
||||
header = ["Tahoe-LAFS Directory SI=", T.span(class_="data-chars")[si_s]]
|
||||
if self.node.is_readonly():
|
||||
if self.node.is_unknown():
|
||||
header.append(" (unknown)")
|
||||
elif not self.node.is_mutable():
|
||||
header.append(" (immutable)")
|
||||
elif self.node.is_readonly():
|
||||
header.append(" (read-only)")
|
||||
return ctx.tag[header]
|
||||
|
||||
@ -603,7 +616,7 @@ class DirectoryAsHTML(rend.Page):
|
||||
return T.div[T.a(href=link)["Return to Welcome page"]]
|
||||
|
||||
def render_show_readonly(self, ctx, data):
|
||||
if self.node.is_readonly():
|
||||
if self.node.is_unknown() or self.node.is_readonly():
|
||||
return ""
|
||||
rocap = self.node.get_readonly_uri()
|
||||
root = get_root(ctx)
|
||||
@ -630,7 +643,7 @@ class DirectoryAsHTML(rend.Page):
|
||||
|
||||
root = get_root(ctx)
|
||||
here = "%s/uri/%s/" % (root, urllib.quote(self.node.get_uri()))
|
||||
if self.node.is_readonly():
|
||||
if self.node.is_unknown() or self.node.is_readonly():
|
||||
delete = "-"
|
||||
rename = "-"
|
||||
else:
|
||||
@ -678,8 +691,8 @@ class DirectoryAsHTML(rend.Page):
|
||||
ctx.fillSlots("times", times)
|
||||
|
||||
assert IFilesystemNode.providedBy(target), target
|
||||
writecap = target.get_uri() or ""
|
||||
quoted_uri = urllib.quote(writecap, safe="") # escape slashes too
|
||||
target_uri = target.get_uri() or ""
|
||||
quoted_uri = urllib.quote(target_uri, safe="") # escape slashes too
|
||||
|
||||
if IMutableFileNode.providedBy(target):
|
||||
# to prevent javascript in displayed .html files from stealing a
|
||||
@ -708,7 +721,7 @@ class DirectoryAsHTML(rend.Page):
|
||||
|
||||
elif IDirectoryNode.providedBy(target):
|
||||
# directory
|
||||
uri_link = "%s/uri/%s/" % (root, urllib.quote(writecap))
|
||||
uri_link = "%s/uri/%s/" % (root, urllib.quote(target_uri))
|
||||
ctx.fillSlots("filename",
|
||||
T.a(href=uri_link)[html.escape(name)])
|
||||
if not target.is_mutable():
|
||||
@ -795,35 +808,30 @@ def DirectoryJSONMetadata(ctx, dirnode):
|
||||
kids = {}
|
||||
for name, (childnode, metadata) in children.iteritems():
|
||||
assert IFilesystemNode.providedBy(childnode), childnode
|
||||
rw_uri = childnode.get_uri()
|
||||
rw_uri = childnode.get_write_uri()
|
||||
ro_uri = childnode.get_readonly_uri()
|
||||
if IFileNode.providedBy(childnode):
|
||||
if childnode.is_readonly():
|
||||
rw_uri = None
|
||||
kiddata = ("filenode", {'size': childnode.get_size(),
|
||||
'mutable': childnode.is_mutable(),
|
||||
})
|
||||
elif IDirectoryNode.providedBy(childnode):
|
||||
if childnode.is_readonly():
|
||||
rw_uri = None
|
||||
kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
|
||||
else:
|
||||
kiddata = ("unknown", {})
|
||||
|
||||
kiddata[1]["metadata"] = metadata
|
||||
if ro_uri:
|
||||
kiddata[1]["ro_uri"] = ro_uri
|
||||
if rw_uri:
|
||||
kiddata[1]["rw_uri"] = rw_uri
|
||||
if ro_uri:
|
||||
kiddata[1]["ro_uri"] = ro_uri
|
||||
verifycap = childnode.get_verify_cap()
|
||||
if verifycap:
|
||||
kiddata[1]['verify_uri'] = verifycap.to_string()
|
||||
|
||||
kids[name] = kiddata
|
||||
if dirnode.is_readonly():
|
||||
drw_uri = None
|
||||
dro_uri = dirnode.get_uri()
|
||||
else:
|
||||
drw_uri = dirnode.get_uri()
|
||||
dro_uri = dirnode.get_readonly_uri()
|
||||
|
||||
drw_uri = dirnode.get_write_uri()
|
||||
dro_uri = dirnode.get_readonly_uri()
|
||||
contents = { 'children': kids }
|
||||
if dro_uri:
|
||||
contents['ro_uri'] = dro_uri
|
||||
@ -834,13 +842,13 @@ def DirectoryJSONMetadata(ctx, dirnode):
|
||||
contents['verify_uri'] = verifycap.to_string()
|
||||
contents['mutable'] = dirnode.is_mutable()
|
||||
data = ("dirnode", contents)
|
||||
return simplejson.dumps(data, indent=1) + "\n"
|
||||
json = simplejson.dumps(data, indent=1) + "\n"
|
||||
return json
|
||||
d.addCallback(_got)
|
||||
d.addCallback(text_plain, ctx)
|
||||
return d
|
||||
|
||||
|
||||
|
||||
def DirectoryURI(ctx, dirnode):
|
||||
return text_plain(dirnode.get_uri(), ctx)
|
||||
|
||||
@ -1132,18 +1140,39 @@ class DeepCheckStreamer(dirnode.DeepStats):
|
||||
self.req.write(j+"\n")
|
||||
return ""
|
||||
|
||||
class UnknownNodeHandler(RenderMixin, rend.Page):
|
||||
|
||||
class UnknownNodeHandler(RenderMixin, rend.Page):
|
||||
def __init__(self, client, node, parentnode=None, name=None):
|
||||
rend.Page.__init__(self)
|
||||
assert node
|
||||
self.node = node
|
||||
self.parentnode = parentnode
|
||||
self.name = name
|
||||
|
||||
def render_GET(self, ctx):
|
||||
req = IRequest(ctx)
|
||||
t = get_arg(req, "t", "").strip()
|
||||
if t == "info":
|
||||
return MoreInfo(self.node)
|
||||
raise WebError("GET unknown URI type: can only do t=info, not t=%s" % t)
|
||||
|
||||
if t == "json":
|
||||
if self.parentnode and self.name:
|
||||
d = self.parentnode.get_metadata_for(self.name)
|
||||
else:
|
||||
d = defer.succeed(None)
|
||||
d.addCallback(lambda md: UnknownJSONMetadata(ctx, self.node, md))
|
||||
return d
|
||||
raise WebError("GET unknown URI type: can only do t=info and t=json, not t=%s.\n"
|
||||
"Using a webapi server that supports a later version of Tahoe "
|
||||
"may help." % t)
|
||||
|
||||
def UnknownJSONMetadata(ctx, filenode, edge_metadata):
|
||||
rw_uri = filenode.get_write_uri()
|
||||
ro_uri = filenode.get_readonly_uri()
|
||||
data = ("unknown", {})
|
||||
if ro_uri:
|
||||
data[1]['ro_uri'] = ro_uri
|
||||
if rw_uri:
|
||||
data[1]['rw_uri'] = rw_uri
|
||||
if edge_metadata is not None:
|
||||
data[1]['metadata'] = edge_metadata
|
||||
return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx)
|
||||
|
@ -6,10 +6,9 @@ from twisted.internet import defer
|
||||
from nevow import url, rend
|
||||
from nevow.inevow import IRequest
|
||||
|
||||
from allmydata.interfaces import ExistingChildError, CannotPackUnknownNodeError
|
||||
from allmydata.interfaces import ExistingChildError
|
||||
from allmydata.monitor import Monitor
|
||||
from allmydata.immutable.upload import FileHandle
|
||||
from allmydata.unknown import UnknownNode
|
||||
from allmydata.util import log, base32
|
||||
|
||||
from allmydata.web.common import text_plain, WebError, RenderMixin, \
|
||||
@ -20,7 +19,6 @@ from allmydata.web.check_results import CheckResults, \
|
||||
from allmydata.web.info import MoreInfo
|
||||
|
||||
class ReplaceMeMixin:
|
||||
|
||||
def replace_me_with_a_child(self, req, client, replace):
|
||||
# a new file is being uploaded in our place.
|
||||
mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
|
||||
@ -55,14 +53,7 @@ class ReplaceMeMixin:
|
||||
def replace_me_with_a_childcap(self, req, client, replace):
|
||||
req.content.seek(0)
|
||||
childcap = req.content.read()
|
||||
childnode = client.create_node_from_uri(childcap, childcap+"readonly")
|
||||
if isinstance(childnode, UnknownNode):
|
||||
# don't be willing to pack unknown nodes: we might accidentally
|
||||
# put some write-authority into the rocap slot because we don't
|
||||
# know how to diminish the URI they gave us. We don't even know
|
||||
# if they gave us a readcap or a writecap.
|
||||
msg = "cannot attach unknown node as child %s" % str(self.name)
|
||||
raise CannotPackUnknownNodeError(msg)
|
||||
childnode = client.create_node_from_uri(childcap, None, name=self.name)
|
||||
d = self.parentnode.set_node(self.name, childnode, overwrite=replace)
|
||||
d.addCallback(lambda res: childnode.get_uri())
|
||||
return d
|
||||
@ -426,12 +417,8 @@ class FileDownloader(rend.Page):
|
||||
|
||||
|
||||
def FileJSONMetadata(ctx, filenode, edge_metadata):
|
||||
if filenode.is_readonly():
|
||||
rw_uri = None
|
||||
ro_uri = filenode.get_uri()
|
||||
else:
|
||||
rw_uri = filenode.get_uri()
|
||||
ro_uri = filenode.get_readonly_uri()
|
||||
rw_uri = filenode.get_write_uri()
|
||||
ro_uri = filenode.get_readonly_uri()
|
||||
data = ("filenode", {})
|
||||
data[1]['size'] = filenode.get_size()
|
||||
if ro_uri:
|
||||
|
@ -21,6 +21,8 @@ class MoreInfo(rend.Page):
|
||||
def get_type(self):
|
||||
node = self.original
|
||||
if IDirectoryNode.providedBy(node):
|
||||
if not node.is_mutable():
|
||||
return "immutable directory"
|
||||
return "directory"
|
||||
if IFileNode.providedBy(node):
|
||||
si = node.get_storage_index()
|
||||
@ -28,7 +30,7 @@ class MoreInfo(rend.Page):
|
||||
if node.is_mutable():
|
||||
return "mutable file"
|
||||
return "immutable file"
|
||||
return "LIT file"
|
||||
return "immutable LIT file"
|
||||
return "unknown"
|
||||
|
||||
def render_title(self, ctx, data):
|
||||
@ -68,10 +70,10 @@ class MoreInfo(rend.Page):
|
||||
|
||||
def render_directory_writecap(self, ctx, data):
|
||||
node = self.original
|
||||
if node.is_readonly():
|
||||
return ""
|
||||
if not IDirectoryNode.providedBy(node):
|
||||
return ""
|
||||
if node.is_readonly():
|
||||
return ""
|
||||
return ctx.tag[node.get_uri()]
|
||||
|
||||
def render_directory_readcap(self, ctx, data):
|
||||
@ -86,27 +88,23 @@ class MoreInfo(rend.Page):
|
||||
return ""
|
||||
return ctx.tag[node.get_verify_cap().to_string()]
|
||||
|
||||
|
||||
def render_file_writecap(self, ctx, data):
|
||||
node = self.original
|
||||
if IDirectoryNode.providedBy(node):
|
||||
node = node._node
|
||||
if ((IDirectoryNode.providedBy(node) or IFileNode.providedBy(node))
|
||||
and node.is_readonly()):
|
||||
write_uri = node.get_write_uri()
|
||||
if not write_uri:
|
||||
return ""
|
||||
writecap = node.get_uri()
|
||||
if not writecap:
|
||||
return ""
|
||||
return ctx.tag[writecap]
|
||||
return ctx.tag[write_uri]
|
||||
|
||||
def render_file_readcap(self, ctx, data):
|
||||
node = self.original
|
||||
if IDirectoryNode.providedBy(node):
|
||||
node = node._node
|
||||
readcap = node.get_readonly_uri()
|
||||
if not readcap:
|
||||
read_uri = node.get_readonly_uri()
|
||||
if not read_uri:
|
||||
return ""
|
||||
return ctx.tag[readcap]
|
||||
return ctx.tag[read_uri]
|
||||
|
||||
def render_file_verifycap(self, ctx, data):
|
||||
node = self.original
|
||||
|
@ -12,7 +12,7 @@ import allmydata # to display import path
|
||||
from allmydata import get_package_versions_string
|
||||
from allmydata import provisioning
|
||||
from allmydata.util import idlib, log
|
||||
from allmydata.interfaces import IFileNode, UnhandledCapTypeError
|
||||
from allmydata.interfaces import IFileNode
|
||||
from allmydata.web import filenode, directory, unlinked, status, operations
|
||||
from allmydata.web import reliability, storage
|
||||
from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
|
||||
@ -85,7 +85,7 @@ class URIHandler(RenderMixin, rend.Page):
|
||||
try:
|
||||
node = self.client.create_node_from_uri(name)
|
||||
return directory.make_handler_for(node, self.client)
|
||||
except (TypeError, UnhandledCapTypeError, AssertionError):
|
||||
except (TypeError, AssertionError):
|
||||
raise WebError("'%s' is not a valid file- or directory- cap"
|
||||
% name)
|
||||
|
||||
@ -104,7 +104,7 @@ class FileHandler(rend.Page):
|
||||
# 'name' must be a file URI
|
||||
try:
|
||||
node = self.client.create_node_from_uri(name)
|
||||
except (TypeError, UnhandledCapTypeError, AssertionError):
|
||||
except (TypeError, AssertionError):
|
||||
# I think this can no longer be reached
|
||||
raise WebError("'%s' is not a valid file- or directory- cap"
|
||||
% name)
|
||||
|
Loading…
Reference in New Issue
Block a user