Prevent mutable objects from being retrieved from an immutable directory, and associated forward-compatibility improvements.

This commit is contained in:
david-sarah 2010-01-26 22:44:30 -08:00
parent 3880486f91
commit 6057bc02cc
27 changed files with 1672 additions and 509 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/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.scripts.common_http import do_http as do_http_req
from allmydata.util.hashutil import tagged_hash from allmydata.util.hashutil import tagged_hash
from allmydata.util.assertutil import precondition from allmydata.util.assertutil import precondition
@ -335,7 +335,7 @@ class TahoeFuseFile(object):
self.fname = self.tfs.cache.tmp_file(os.urandom(20)) self.fname = self.tfs.cache.tmp_file(os.urandom(20))
if self.fnode is None: if self.fnode is None:
log('TFF: [%s] open() for write: no file node, creating new File %s' % (self.name, self.fname, )) 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.fnode.tmp_fname = self.fname # XXX kill this
self.parent.add_child(self.name, self.fnode, {}) self.parent.add_child(self.name, self.fnode, {})
elif hasattr(self.fnode, 'tmp_fname'): elif hasattr(self.fnode, 'tmp_fname'):
@ -362,7 +362,7 @@ class TahoeFuseFile(object):
self.fname = self.fnode.tmp_fname self.fname = self.fnode.tmp_fname
log('TFF: reopening(%s) for reading' % self.fname) log('TFF: reopening(%s) for reading' % self.fname)
else: 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') log('TFF: synchronously fetching file from cache for reading')
self.fname = self.tfs.cache.get_file(uri) self.fname = self.tfs.cache.get_file(uri)
else: else:
@ -1237,7 +1237,7 @@ class FileCache(object):
def get_file(self, uri): def get_file(self, uri):
self.log('get_file(%s)' % (uri,)) self.log('get_file(%s)' % (uri,))
if uri.startswith("URI:LIT"): if is_literal_file_uri(uri):
return self.get_literal(uri) return self.get_literal(uri)
else: else:
return self.get_chk(uri, async=False) return self.get_chk(uri, async=False)

View File

@ -150,8 +150,12 @@ server prefix. They will be displayed like this:
=== Child Lookup === === Child Lookup ===
Tahoe directories contain named children, just like directories in a regular Tahoe directories contain named child entries, just like directories in a regular
local filesystem. These children can be either files or subdirectories. 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 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 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 Note that the webapi-using client application must not provide the
"Content-Type: multipart/form-data" header that usually accompanies HTML "Content-Type: multipart/form-data" header that usually accompanies HTML
form submissions, since the body is not formatted this way. Doing so will 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 Like t=mkdir-with-children above, but the new directory will be
deep-immutable. This means that the directory itself is immutable, and that deep-immutable. This means that the directory itself is immutable, and that
it can only contain deep-immutable objects, like immutable files, literal it can only contain objects that are treated as being deep-immutable, like
files, and deep-immutable directories. A non-empty request body is immutable files, literal files, and deep-immutable directories.
mandatory, since after the directory is created, it will not be possible to
add more children to it. 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 POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
PUT /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 Create new directories as necessary to make sure that the named target
($DIRCAP/SUBDIRS../SUBDIR) is a directory. This will create additional ($DIRCAP/SUBDIRS../SUBDIR) is a directory. This will create additional
intermediate directories as necessary. If the named target directory already intermediate mutable directories as necessary. If the named target directory
exists, this will make no changes to it. already exists, this will make no changes to it.
If the final directory is created, it will be empty. 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 This operation will return an error if a blocking file is present at any of
names, preventing the server from creating the necessary parent directory. 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 The write-cap of the new directory will be returned as the HTTP response
body. body.
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-with-children POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-with-children
Like above, but if the final directory is created, it will be populated with Like /uri?t=mkdir-with-children, but the final directory is created as a
initial children from the POST request body, as described above in the child of an existing mutable directory. This will create additional
/uri?t=mkdir-with-children operation. 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 POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-immutable
Like above, but the final directory will be deep-immutable, with the Like /uri?t=mkdir-immutable, but the final directory is created as a child
children specified as a JSON dictionary in the POST request body. 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 POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME
Create a new empty directory and attach it to the given existing directory. Create a new empty mutable directory and attach it to the given existing
This will create additional intermediate directories as necessary. directory. This will create additional intermediate directories as necessary.
The URL of this form points to the parent of the bottom-most new directory, This operation will return an error if a blocking file is present at any of
whereas the previous form has a URL that points directly to the bottom-most the parent names, preventing the server from creating the necessary parent
new directory. 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 POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME
As above, but the new directory will be populated with initial children via Like /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME, but the new directory will
the POST request body, as described in /uri?t=mkdir-with-children above. 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 Note that the name= argument must be passed as a queryarg, because the POST
request body is used for the initial children JSON. request body is used for the initial children JSON.
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-immutable&name=NAME POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-immutable&name=NAME
As above, but the new directory will be deep-immutable, with the children Like /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME, but the
specified as a JSON dictionary in the POST request body. Again, the name= final directory will be deep-immutable. The children are specified as a
argument must be passed as a queryarg. 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) === === 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 "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 "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 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 When the set_children request contains a child name that already exists in
the target directory, this command defaults to overwriting that child with 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 POST /uri/$DIRCAP/[SUBDIRS../]?t=upload
This uploads a file, and attaches it as a new child of the given directory. 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, which must be mutable. The file must be provided as the "file" field of an
produced in response to an HTML form like this: HTML-encoded form body, produced in response to an HTML form like this:
<form action="." method="POST" enctype="multipart/form-data"> <form action="." method="POST" enctype="multipart/form-data">
<input type="hidden" name="t" value="upload" /> <input type="hidden" name="t" value="upload" />
<input type="file" name="file" /> <input type="file" name="file" />
@ -925,9 +1006,10 @@ POST /uri/$DIRCAP/[SUBDIRS../]?t=upload
POST /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=upload POST /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=upload
This also uploads a file and attaches it as a new child of the given 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 directory, which must be mutable. It is a slight variant of the previous
refers to the target file rather than the parent directory. It is otherwise operation, as the URL refers to the target file rather than the parent
identical: this accepts mutable= and when_done= arguments too. directory. It is otherwise identical: this accepts mutable= and when_done=
arguments too.
POST /uri/$FILECAP?t=upload 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 POST /uri/$DIRCAP/[SUBDIRS../]?t=delete&name=CHILDNAME
This instructs the node to delete a child object (file or subdirectory) from This instructs the node to remove a child object (file or subdirectory) from
the given directory. Note that the entire subtree is removed. This is the given directory, which must be mutable. Note that the entire subtree is
somewhat like "rm -rf" (from the point of view of the parent), but other unlinked from the parent. Unlike deleting a subdirectory in a UNIX local
references into the subtree will see that the child subdirectories are not filesystem, the subtree need not be empty; if it isn't, then other references
modified by this operation. Only the link from the given directory to its into the subtree will see that the child subdirectories are not modified by
child is severed. this operation. Only the link from the given directory to its child is severed.
=== Renaming A Child === === Renaming A Child ===
POST /uri/$DIRCAP/[SUBDIRS../]?t=rename&from_name=OLD&to_name=NEW 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 This instructs the node to rename a child of the given directory, which must
exactly the same as removing the child, then adding the same child-cap under be mutable. This has a similar effect to removing the child, then adding the
the new name. This operation cannot move the child to a different directory. 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 This operation will replace any existing child of the new name, making it
behave like the UNIX "mv -f" command. 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 "path": a list of strings, with the path that is traversed to reach the
object object
"cap": a writecap for the file or directory, if available, else a readcap "cap": a write-cap URI for the file or directory, if available, else a
"verifycap": a verifycap for the file or directory read-cap URI
"repaircap": the weakest cap which can still be used to repair the object "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 "storage-index": a base32 storage index for the object
"check-results": a copy of the dictionary which would be returned by "check-results": a copy of the dictionary which would be returned by
t=check&output=json, with three top-level keys: 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 "path": a list of strings, with the path that is traversed to reach the
object object
"cap": a writecap for the file or directory, if available, else a readcap "cap": a write-cap URI for the file or directory, if available, else a
"verifycap": a verifycap for the file or directory read-cap URI
"repaircap": the weakest cap which can still be used to repair the object "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 "storage-index": a base32 storage index for the object
Note that non-distributed files (i.e. LIT files) will have values of None 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 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. 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 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 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". request reaches the server". Use URIs if you want "this particular object".

View File

@ -471,13 +471,16 @@ class Client(node.Node, pollmixin.PollMixin):
# dirnodes. The first takes a URI and produces a filenode or (new-style) # dirnodes. The first takes a URI and produces a filenode or (new-style)
# dirnode. The other three create brand-new filenodes/dirnodes. # dirnode. The other three create brand-new filenodes/dirnodes.
def create_node_from_uri(self, writecap, readcap=None): def create_node_from_uri(self, write_uri, read_uri=None, deep_immutable=False, name="<unknown name>"):
# this returns synchronously. # This returns synchronously.
return self.nodemaker.create_from_cap(writecap, readcap) # 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={}): def create_dirnode(self, initial_children={}):
d = self.nodemaker.create_new_mutable_directory(initial_children) d = self.nodemaker.create_new_mutable_directory(initial_children)
return d return d
def create_immutable_dirnode(self, children, convergence=None): def create_immutable_dirnode(self, children, convergence=None):
return self.nodemaker.create_immutable_directory(children, convergence) return self.nodemaker.create_immutable_directory(children, convergence)

View File

@ -5,7 +5,7 @@ from twisted.application import service
from twisted.internet import defer from twisted.internet import defer
from twisted.internet.interfaces import IConsumer from twisted.internet.interfaces import IConsumer
from foolscap.api import Referenceable from foolscap.api import Referenceable
from allmydata.interfaces import RIControlClient from allmydata.interfaces import RIControlClient, IFileNode
from allmydata.util import fileutil, mathutil from allmydata.util import fileutil, mathutil
from allmydata.immutable import upload from allmydata.immutable import upload
from twisted.python import log from twisted.python import log
@ -67,7 +67,9 @@ class ControlServer(Referenceable, service.Service):
return d return d
def remote_download_from_uri_to_file(self, uri, filename): 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) c = FileWritingConsumer(filename)
d = filenode.read(c) d = filenode.read(c)
d.addCallback(lambda res: filename) d.addCallback(lambda res: filename)
@ -199,6 +201,8 @@ class SpeedTest:
if i >= self.count: if i >= self.count:
return return
n = self.parent.create_node_from_uri(self.uris[i]) 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(): if n.is_mutable():
d1 = n.download_best_version() d1 = n.download_best_version()
else: else:

View File

@ -5,13 +5,13 @@ from zope.interface import implements
from twisted.internet import defer from twisted.internet import defer
from foolscap.api import fireEventually from foolscap.api import fireEventually
import simplejson import simplejson
from allmydata.mutable.common import NotMutableError from allmydata.mutable.common import NotWriteableError
from allmydata.mutable.filenode import MutableFileNode 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, \ from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
IImmutableFileNode, IMutableFileNode, \ IImmutableFileNode, IMutableFileNode, \
ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \ ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
CannotPackUnknownNodeError MustBeDeepImmutableError, CapConstraintError
from allmydata.check_results import DeepCheckResults, \ from allmydata.check_results import DeepCheckResults, \
DeepCheckAndRepairResults DeepCheckAndRepairResults
from allmydata.monitor import Monitor from allmydata.monitor import Monitor
@ -40,6 +40,7 @@ class Deleter:
new_contents = self.node._pack_contents(children) new_contents = self.node._pack_contents(children)
return new_contents return new_contents
class MetadataSetter: class MetadataSetter:
def __init__(self, node, name, metadata): def __init__(self, node, name, metadata):
self.node = node self.node = node
@ -75,6 +76,11 @@ class Adder:
for (name, (child, new_metadata)) in self.entries.iteritems(): for (name, (child, new_metadata)) in self.entries.iteritems():
precondition(isinstance(name, unicode), name) precondition(isinstance(name, unicode), name)
precondition(IFilesystemNode.providedBy(child), child) 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 name in children:
if not self.overwrite: if not self.overwrite:
raise ExistingChildError("child '%s' already exists" % name) raise ExistingChildError("child '%s' already exists" % name)
@ -123,25 +129,21 @@ class Adder:
new_contents = self.node._pack_contents(children) new_contents = self.node._pack_contents(children)
return new_contents return new_contents
def _encrypt_rwcap(filenode, rwcap): def _encrypt_rw_uri(filenode, rw_uri):
assert isinstance(rwcap, str) assert isinstance(rw_uri, str)
writekey = filenode.get_writekey() writekey = filenode.get_writekey()
if not writekey: if not writekey:
return "" return ""
salt = hashutil.mutable_rwcap_salt_hash(rwcap) salt = hashutil.mutable_rwcap_salt_hash(rw_uri)
key = hashutil.mutable_rwcap_key_hash(salt, writekey) key = hashutil.mutable_rwcap_key_hash(salt, writekey)
cryptor = AES(key) cryptor = AES(key)
crypttext = cryptor.process(rwcap) crypttext = cryptor.process(rw_uri)
mac = hashutil.hmac(key, salt + crypttext) mac = hashutil.hmac(key, salt + crypttext)
assert len(mac) == 32 assert len(mac) == 32
return salt + crypttext + mac return salt + crypttext + mac
# The MAC is not checked by readers in Tahoe >= 1.3.0, but we still # The MAC is not checked by readers in Tahoe >= 1.3.0, but we still
# produce it for the sake of older readers. # 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): def pack_children(filenode, children, deep_immutable=False):
"""Take a dict that maps: """Take a dict that maps:
children[unicode_name] = (IFileSystemNode, metadata_dict) children[unicode_name] = (IFileSystemNode, metadata_dict)
@ -152,7 +154,7 @@ def pack_children(filenode, children, deep_immutable=False):
time. time.
If deep_immutable is True, I will require that all my children are deeply 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) has_aux = isinstance(children, AuxValueDict)
@ -161,25 +163,29 @@ def pack_children(filenode, children, deep_immutable=False):
assert isinstance(name, unicode) assert isinstance(name, unicode)
entry = None entry = None
(child, metadata) = children[name] (child, metadata) = children[name]
if deep_immutable and child.is_mutable(): child.raise_error()
# TODO: consider adding IFileSystemNode.is_deep_immutable() if deep_immutable and not child.is_allowed_in_immutable_directory():
raise MustBeDeepImmutable("child '%s' is mutable" % (name,)) raise MustBeDeepImmutableError("child '%s' is not allowed in an immutable directory" % (name,), name)
if has_aux: if has_aux:
entry = children.get_aux(name) entry = children.get_aux(name)
if not entry: if not entry:
assert IFilesystemNode.providedBy(child), (name,child) assert IFilesystemNode.providedBy(child), (name,child)
assert isinstance(metadata, dict) assert isinstance(metadata, dict)
rwcap = child.get_uri() # might be RO if the child is not writeable rw_uri = child.get_write_uri()
if rwcap is None: if rw_uri is None:
rwcap = "" rw_uri = ""
assert isinstance(rwcap, str), rwcap assert isinstance(rw_uri, str), rw_uri
rocap = child.get_readonly_uri()
if rocap is None: # should be prevented by MustBeDeepImmutableError check above
rocap = "" assert not (rw_uri and deep_immutable)
assert isinstance(rocap, str), rocap
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")), entry = "".join([netstring(name.encode("utf-8")),
netstring(rocap), netstring(strip_prefix_for_ro(ro_uri, deep_immutable)),
netstring(_encrypt_rwcap(filenode, rwcap)), netstring(_encrypt_rw_uri(filenode, rw_uri)),
netstring(simplejson.dumps(metadata))]) netstring(simplejson.dumps(metadata))])
entries.append(netstring(entry)) entries.append(netstring(entry))
return "".join(entries) return "".join(entries)
@ -230,38 +236,64 @@ class DirectoryNode:
plaintext = cryptor.process(crypttext) plaintext = cryptor.process(crypttext)
return plaintext return plaintext
def _create_node(self, rwcap, rocap): def _create_and_validate_node(self, rw_uri, ro_uri, name):
return self._nodemaker.create_from_cap(rwcap, rocap) 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): def _unpack_contents(self, data):
# the directory is serialized as a list of netstrings, one per child. # the directory is serialized as a list of netstrings, one per child.
# Each child is serialized as a list of four netstrings: (name, # Each child is serialized as a list of four netstrings: (name, ro_uri,
# rocap, rwcap, metadata), in which the name,rocap,metadata are in # rwcapdata, metadata), in which the name, ro_uri, metadata are in
# cleartext. The 'name' is UTF-8 encoded. The rwcap is formatted as: # cleartext. The 'name' is UTF-8 encoded. The rwcapdata is formatted as:
# pack("16ss32s", iv, AES(H(writekey+iv), plaintextrwcap), mac) # pack("16ss32s", iv, AES(H(writekey+iv), plaintext_rw_uri), mac)
assert isinstance(data, str), (repr(data), type(data)) assert isinstance(data, str), (repr(data), type(data))
# an empty directory is serialized as an empty string # an empty directory is serialized as an empty string
if data == "": if data == "":
return AuxValueDict() return AuxValueDict()
writeable = not self.is_readonly() writeable = not self.is_readonly()
mutable = self.is_mutable()
children = AuxValueDict() children = AuxValueDict()
position = 0 position = 0
while position < len(data): while position < len(data):
entries, position = split_netstring(data, 1, position) entries, position = split_netstring(data, 1, position)
entry = entries[0] 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") name = name.decode("utf-8")
rwcap = None rw_uri = ""
if writeable: if writeable:
rwcap = self._decrypt_rwcapdata(rwcapdata) rw_uri = self._decrypt_rwcapdata(rwcapdata)
if not rwcap:
rwcap = None # rwcap is None or a non-empty string # Since the encryption uses CTR mode, it currently leaks the length of the
if not rocap: # plaintext rw_uri -- and therefore whether it is present, i.e. whether the
rocap = None # rocap is None or a non-empty string # dirnode is writeable (ticket #925). By stripping spaces in Tahoe >= 1.6.0,
child = self._create_node(rwcap, rocap) # we may make it easier for future versions to plug this leak.
metadata = simplejson.loads(metadata_s) # ro_uri is treated in the same way for consistency.
assert isinstance(metadata, dict) # rw_uri and ro_uri will be either None or a non-empty string.
children.set_with_aux(name, (child, metadata), auxilliary=entry)
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 return children
def _pack_contents(self, children): def _pack_contents(self, children):
@ -270,21 +302,39 @@ class DirectoryNode:
def is_readonly(self): def is_readonly(self):
return self._node.is_readonly() return self._node.is_readonly()
def is_mutable(self): def is_mutable(self):
return self._node.is_mutable() 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): def get_uri(self):
return self._uri.to_string() 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): def get_readonly_uri(self):
return self._uri.get_readonly().to_string() return self._uri.get_readonly().to_string()
def get_cap(self): def get_cap(self):
return self._uri return self._uri
def get_readcap(self): def get_readcap(self):
return self._uri.get_readonly() return self._uri.get_readonly()
def get_verify_cap(self): def get_verify_cap(self):
return self._uri.get_verify_cap() return self._uri.get_verify_cap()
def get_repair_cap(self): def get_repair_cap(self):
if self._node.is_readonly(): if self._node.is_readonly():
return None # readonly (mutable) dirnodes are not yet repairable return None # readonly (mutable) dirnodes are not yet repairable
@ -350,7 +400,7 @@ class DirectoryNode:
def set_metadata_for(self, name, metadata): def set_metadata_for(self, name, metadata):
assert isinstance(name, unicode) assert isinstance(name, unicode)
if self.is_readonly(): if self.is_readonly():
return defer.fail(NotMutableError()) return defer.fail(NotWriteableError())
assert isinstance(metadata, dict) assert isinstance(metadata, dict)
s = MetadataSetter(self, name, metadata) s = MetadataSetter(self, name, metadata)
d = self._node.modify(s.modify) d = self._node.modify(s.modify)
@ -398,14 +448,10 @@ class DirectoryNode:
precondition(isinstance(name, unicode), name) precondition(isinstance(name, unicode), name)
precondition(isinstance(writecap, (str,type(None))), writecap) precondition(isinstance(writecap, (str,type(None))), writecap)
precondition(isinstance(readcap, (str,type(None))), readcap) precondition(isinstance(readcap, (str,type(None))), readcap)
child_node = self._create_node(writecap, readcap)
if isinstance(child_node, UnknownNode): # We now allow packing unknown nodes, provided they are valid
# don't be willing to pack unknown nodes: we might accidentally # for this type of directory.
# put some write-authority into the rocap slot because we don't child_node = self._create_and_validate_node(writecap, readcap, name)
# 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)
d = self.set_node(name, child_node, metadata, overwrite) d = self.set_node(name, child_node, metadata, overwrite)
d.addCallback(lambda res: child_node) d.addCallback(lambda res: child_node)
return d return d
@ -423,10 +469,10 @@ class DirectoryNode:
writecap, readcap, metadata = e writecap, readcap, metadata = e
precondition(isinstance(writecap, (str,type(None))), writecap) precondition(isinstance(writecap, (str,type(None))), writecap)
precondition(isinstance(readcap, (str,type(None))), readcap) precondition(isinstance(readcap, (str,type(None))), readcap)
child_node = self._create_node(writecap, readcap)
if isinstance(child_node, UnknownNode): # We now allow packing unknown nodes, provided they are valid
msg = "cannot pack unknown node as child %s" % str(name) # for this type of directory.
raise CannotPackUnknownNodeError(msg) child_node = self._create_and_validate_node(writecap, readcap, name)
a.set_node(name, child_node, metadata) a.set_node(name, child_node, metadata)
d = self._node.modify(a.modify) d = self._node.modify(a.modify)
d.addCallback(lambda ign: self) d.addCallback(lambda ign: self)
@ -439,12 +485,12 @@ class DirectoryNode:
same name. same name.
If this directory node is read-only, the Deferred will errback with a If this directory node is read-only, the Deferred will errback with a
NotMutableError.""" NotWriteableError."""
precondition(IFilesystemNode.providedBy(child), child) precondition(IFilesystemNode.providedBy(child), child)
if self.is_readonly(): if self.is_readonly():
return defer.fail(NotMutableError()) return defer.fail(NotWriteableError())
assert isinstance(name, unicode) assert isinstance(name, unicode)
assert IFilesystemNode.providedBy(child), child assert IFilesystemNode.providedBy(child), child
a = Adder(self, overwrite=overwrite) a = Adder(self, overwrite=overwrite)
@ -456,7 +502,7 @@ class DirectoryNode:
def set_nodes(self, entries, overwrite=True): def set_nodes(self, entries, overwrite=True):
precondition(isinstance(entries, dict), entries) precondition(isinstance(entries, dict), entries)
if self.is_readonly(): if self.is_readonly():
return defer.fail(NotMutableError()) return defer.fail(NotWriteableError())
a = Adder(self, entries, overwrite=overwrite) a = Adder(self, entries, overwrite=overwrite)
d = self._node.modify(a.modify) d = self._node.modify(a.modify)
d.addCallback(lambda res: self) d.addCallback(lambda res: self)
@ -470,10 +516,10 @@ class DirectoryNode:
the operation completes.""" the operation completes."""
assert isinstance(name, unicode) assert isinstance(name, unicode)
if self.is_readonly(): if self.is_readonly():
return defer.fail(NotMutableError()) return defer.fail(NotWriteableError())
d = self._uploader.upload(uploadable) d = self._uploader.upload(uploadable)
d.addCallback(lambda results: results.uri) d.addCallback(lambda results:
d.addCallback(self._nodemaker.create_from_cap) self._create_and_validate_node(results.uri, None, name))
d.addCallback(lambda node: d.addCallback(lambda node:
self.set_node(name, node, metadata, overwrite)) self.set_node(name, node, metadata, overwrite))
return d return d
@ -483,7 +529,7 @@ class DirectoryNode:
fires (with the node just removed) when the operation finishes.""" fires (with the node just removed) when the operation finishes."""
assert isinstance(name, unicode) assert isinstance(name, unicode)
if self.is_readonly(): if self.is_readonly():
return defer.fail(NotMutableError()) return defer.fail(NotWriteableError())
deleter = Deleter(self, name) deleter = Deleter(self, name)
d = self._node.modify(deleter.modify) d = self._node.modify(deleter.modify)
d.addCallback(lambda res: deleter.old_child) d.addCallback(lambda res: deleter.old_child)
@ -493,7 +539,7 @@ class DirectoryNode:
mutable=True): mutable=True):
assert isinstance(name, unicode) assert isinstance(name, unicode)
if self.is_readonly(): if self.is_readonly():
return defer.fail(NotMutableError()) return defer.fail(NotWriteableError())
if mutable: if mutable:
d = self._nodemaker.create_new_mutable_directory(initial_children) d = self._nodemaker.create_new_mutable_directory(initial_children)
else: else:
@ -515,7 +561,7 @@ class DirectoryNode:
Deferred that fires when the operation finishes.""" Deferred that fires when the operation finishes."""
assert isinstance(current_child_name, unicode) assert isinstance(current_child_name, unicode)
if self.is_readonly() or new_parent.is_readonly(): if self.is_readonly() or new_parent.is_readonly():
return defer.fail(NotMutableError()) return defer.fail(NotWriteableError())
if new_child_name is None: if new_child_name is None:
new_child_name = current_child_name new_child_name = current_child_name
assert isinstance(new_child_name, unicode) assert isinstance(new_child_name, unicode)

View File

@ -17,6 +17,9 @@ from allmydata.immutable import download
class _ImmutableFileNodeBase(object): class _ImmutableFileNodeBase(object):
implements(IImmutableFileNode, ICheckable) implements(IImmutableFileNode, ICheckable)
def get_write_uri(self):
return None
def get_readonly_uri(self): def get_readonly_uri(self):
return self.get_uri() return self.get_uri()
@ -26,6 +29,15 @@ class _ImmutableFileNodeBase(object):
def is_readonly(self): def is_readonly(self):
return True 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): def __hash__(self):
return self.u.__hash__() return self.u.__hash__()
def __eq__(self, other): def __eq__(self, other):

View File

@ -456,7 +456,6 @@ class IVerifierURI(Interface, IURI):
class IDirnodeURI(Interface): class IDirnodeURI(Interface):
"""I am a URI which represents a dirnode.""" """I am a URI which represents a dirnode."""
class IFileURI(Interface): class IFileURI(Interface):
"""I am a URI which represents a filenode.""" """I am a URI which represents a filenode."""
def get_size(): def get_size():
@ -467,21 +466,28 @@ class IImmutableFileURI(IFileURI):
class IMutableFileURI(Interface): class IMutableFileURI(Interface):
"""I am a URI which represents a mutable filenode.""" """I am a URI which represents a mutable filenode."""
class IDirectoryURI(Interface): class IDirectoryURI(Interface):
pass pass
class IReadonlyDirectoryURI(Interface): class IReadonlyDirectoryURI(Interface):
pass pass
class CannotPackUnknownNodeError(Exception): class CapConstraintError(Exception):
"""UnknownNodes (using filecaps from the future that we don't understand) """A constraint on a cap was violated."""
cannot yet be copied safely, so I refuse to copy them."""
class UnhandledCapTypeError(Exception): class MustBeDeepImmutableError(CapConstraintError):
"""I recognize the cap/URI, but I cannot create an IFilesystemNode for """Mutable children cannot be added to an immutable directory.
it.""" 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): class MustBeReadonlyError(CapConstraintError):
"""Deep-immutable directories can only contain deep-immutable children""" """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: # The hierarchy looks like this:
# IFilesystemNode # IFilesystemNode
@ -518,9 +524,8 @@ class IFilesystemNode(Interface):
""" """
def get_uri(): def get_uri():
""" """Return the URI string corresponding to the strongest cap associated
Return the URI string that can be used by others to get access to with this node. If this node is read-only, the URI will only offer
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-only access. If this node is read-write, the URI will offer
read-write access. read-write access.
@ -528,6 +533,11 @@ class IFilesystemNode(Interface):
read-only access with others, use get_readonly_uri(). 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(): def get_readonly_uri():
"""Return the URI string that can be used by others to get read-only """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 access to this node. The result is a read-only URI, regardless of
@ -557,6 +567,18 @@ class IFilesystemNode(Interface):
file. 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(): def get_size():
"""Return the length (in bytes) of the data this node represents. For """Return the length (in bytes) of the data this node represents. For
directory nodes, I return the size of the backing store. I return 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. ctime/mtime semantics of traditional filesystems.
If this directory node is read-only, the Deferred will errback with a If this directory node is read-only, the Deferred will errback with a
NotMutableError.""" NotWriteableError."""
def set_children(entries, overwrite=True): def set_children(entries, overwrite=True):
"""Add multiple children (by writecap+readcap) to a directory node. """Add multiple children (by writecap+readcap) to a directory node.
@ -928,7 +950,7 @@ class IDirectoryNode(IFilesystemNode):
ctime/mtime semantics of traditional filesystems. ctime/mtime semantics of traditional filesystems.
If this directory node is read-only, the Deferred will errback with a If this directory node is read-only, the Deferred will errback with a
NotMutableError.""" NotWriteableError."""
def set_nodes(entries, overwrite=True): def set_nodes(entries, overwrite=True):
"""Add multiple children to a directory node. Takes a dict mapping """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 Tahoe process will typically have a single NodeMaker, but unit tests may
create simplified/mocked forms for testing purposes. 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 """I create an IFilesystemNode from the given writecap/readcap. I can
only provide nodes for existing file/directory objects: use my other only provide nodes for existing file/directory objects: use my other
methods to create new objects. I return synchronously.""" methods to create new objects. I return synchronously."""

View File

@ -8,7 +8,7 @@ MODE_WRITE = "MODE_WRITE" # replace all shares, probably.. not for initial
# creation # creation
MODE_READ = "MODE_READ" MODE_READ = "MODE_READ"
class NotMutableError(Exception): class NotWriteableError(Exception):
pass pass
class NeedMoreDataError(Exception): class NeedMoreDataError(Exception):

View File

@ -214,6 +214,12 @@ class MutableFileNode:
def get_uri(self): def get_uri(self):
return self._uri.to_string() 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): def get_readonly_uri(self):
return self._uri.get_readonly().to_string() return self._uri.get_readonly().to_string()
@ -227,9 +233,19 @@ class MutableFileNode:
def is_mutable(self): def is_mutable(self):
return self._uri.is_mutable() return self._uri.is_mutable()
def is_readonly(self): def is_readonly(self):
return self._uri.is_readonly() 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): def __hash__(self):
return hash((self.__class__, self._uri)) return hash((self.__class__, self._uri))
def __cmp__(self, them): def __cmp__(self, them):

View File

@ -1,7 +1,7 @@
import weakref import weakref
from zope.interface import implements from zope.interface import implements
from allmydata.util.assertutil import precondition 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.filenode import ImmutableFileNode, LiteralFileNode
from allmydata.immutable.upload import Data from allmydata.immutable.upload import Data
from allmydata.mutable.filenode import MutableFileNode from allmydata.mutable.filenode import MutableFileNode
@ -44,28 +44,33 @@ class NodeMaker:
def _create_dirnode(self, filenode): def _create_dirnode(self, filenode):
return DirectoryNode(filenode, self, self.uploader) 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". # this returns synchronously. It starts with a "cap string".
assert isinstance(writecap, (str, type(None))), type(writecap) assert isinstance(writecap, (str, type(None))), type(writecap)
assert isinstance(readcap, (str, type(None))), type(readcap) assert isinstance(readcap, (str, type(None))), type(readcap)
bigcap = writecap or readcap bigcap = writecap or readcap
if not bigcap: if not bigcap:
# maybe the writecap was hidden because we're in a readonly # maybe the writecap was hidden because we're in a readonly
# directory, and the future cap format doesn't have a readcap, or # directory, and the future cap format doesn't have a readcap, or
# something. # something.
return UnknownNode(writecap, readcap) return UnknownNode(None, None) # deep_immutable and name not needed
if bigcap in self._node_cache:
return self._node_cache[bigcap] # The name doesn't matter for caching since it's only used in the error
cap = uri.from_string(bigcap) # attribute of an UnknownNode, and we don't cache those.
node = self._create_from_cap(cap) 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: if node:
self._node_cache[bigcap] = node # note: WeakValueDictionary self._node_cache[memokey] = node # note: WeakValueDictionary
else: 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 return node
def _create_from_cap(self, cap): def _create_from_single_cap(self, cap):
# This starts with a "cap instance"
if isinstance(cap, uri.LiteralFileURI): if isinstance(cap, uri.LiteralFileURI):
return self._create_lit(cap) return self._create_lit(cap)
if isinstance(cap, uri.CHKFileURI): if isinstance(cap, uri.CHKFileURI):
@ -76,7 +81,7 @@ class NodeMaker:
uri.ReadonlyDirectoryURI, uri.ReadonlyDirectoryURI,
uri.ImmutableDirectoryURI, uri.ImmutableDirectoryURI,
uri.LiteralDirectoryURI)): 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 self._create_dirnode(filenode)
return None return None
@ -89,13 +94,11 @@ class NodeMaker:
return d return d
def create_new_mutable_directory(self, initial_children={}): def create_new_mutable_directory(self, initial_children={}):
# initial_children must have metadata (i.e. {} instead of None), and # initial_children must have metadata (i.e. {} instead of None)
# should not contain UnknownNodes
for (name, (node, metadata)) in initial_children.iteritems(): 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), precondition(isinstance(metadata, dict),
"create_new_mutable_directory requires metadata to be a dict, not None", metadata) "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
node.raise_error()
d = self.create_mutable_file(lambda n: d = self.create_mutable_file(lambda n:
pack_children(n, initial_children)) pack_children(n, initial_children))
d.addCallback(self._create_dirnode) d.addCallback(self._create_dirnode)
@ -105,19 +108,15 @@ class NodeMaker:
if convergence is None: if convergence is None:
convergence = self.secret_holder.get_convergence_secret() convergence = self.secret_holder.get_convergence_secret()
for (name, (node, metadata)) in children.iteritems(): for (name, (node, metadata)) in children.iteritems():
precondition(not isinstance(node, UnknownNode),
"create_immutable_directory does not accept UnknownNode", node)
precondition(isinstance(metadata, dict), precondition(isinstance(metadata, dict),
"create_immutable_directory requires metadata to be a dict, not None", metadata) "create_immutable_directory requires metadata to be a dict, not None", metadata)
if node.is_mutable(): node.raise_error()
raise NotDeepImmutableError("%s is not immutable" % (node,)) if not node.is_allowed_in_immutable_directory():
raise MustBeDeepImmutableError("%s is not immutable" % (node,), name)
n = DummyImmutableFileNode() # writekey=None n = DummyImmutableFileNode() # writekey=None
packed = pack_children(n, children) packed = pack_children(n, children)
uploadable = Data(packed, convergence) uploadable = Data(packed, convergence)
d = self.uploader.upload(uploadable, history=self.history) d = self.uploader.upload(uploadable, history=self.history)
def _uploaded(results): d.addCallback(lambda results: self.create_from_cap(None, results.uri))
filecap = self.create_from_cap(results.uri)
return filecap
d.addCallback(_uploaded)
d.addCallback(self._create_dirnode) d.addCallback(self._create_dirnode)
return d return d

View File

@ -128,12 +128,14 @@ class UnknownAliasError(Exception):
pass pass
def get_alias(aliases, path, default): def get_alias(aliases, path, default):
from allmydata import uri
# transform "work:path/filename" into (aliases["work"], "path/filename"). # transform "work:path/filename" into (aliases["work"], "path/filename").
# If default=None, then an empty alias is indicated by returning # If default=None, then an empty alias is indicated by returning
# DefaultAliasMarker. We special-case "URI:" to make it easy to access # DefaultAliasMarker. We special-case strings with a recognized cap URI
# specific files/directories by their read-cap. # prefix, to make it easy to access specific files/directories by their
# caps.
path = path.strip() 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 # The only way to get a sub-path is to use URI:blah:./foo, and we
# strip out the :./ sequence. # strip out the :./ sequence.
sep = path.find(":./") sep = path.find(":./")

View File

@ -258,8 +258,7 @@ class TahoeDirectorySource:
readcap = ascii_or_none(data[1].get("ro_uri")) readcap = ascii_or_none(data[1].get("ro_uri"))
self.children[name] = TahoeFileSource(self.nodeurl, mutable, self.children[name] = TahoeFileSource(self.nodeurl, mutable,
writecap, readcap) writecap, readcap)
else: elif data[0] == "dirnode":
assert data[0] == "dirnode"
writecap = ascii_or_none(data[1].get("rw_uri")) writecap = ascii_or_none(data[1].get("rw_uri"))
readcap = ascii_or_none(data[1].get("ro_uri")) readcap = ascii_or_none(data[1].get("ro_uri"))
if writecap and writecap in self.cache: if writecap and writecap in self.cache:
@ -277,6 +276,11 @@ class TahoeDirectorySource:
if recurse: if recurse:
child.populate(True) child.populate(True)
self.children[name] = child 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: class TahoeMissingTarget:
def __init__(self, url): def __init__(self, url):
@ -353,8 +357,7 @@ class TahoeDirectoryTarget:
urllib.quote(name.encode('utf-8'))]) urllib.quote(name.encode('utf-8'))])
self.children[name] = TahoeFileTarget(self.nodeurl, mutable, self.children[name] = TahoeFileTarget(self.nodeurl, mutable,
writecap, readcap, url) writecap, readcap, url)
else: elif data[0] == "dirnode":
assert data[0] == "dirnode"
writecap = ascii_or_none(data[1].get("rw_uri")) writecap = ascii_or_none(data[1].get("rw_uri"))
readcap = ascii_or_none(data[1].get("ro_uri")) readcap = ascii_or_none(data[1].get("ro_uri"))
if writecap and writecap in self.cache: if writecap and writecap in self.cache:
@ -372,6 +375,11 @@ class TahoeDirectoryTarget:
if recurse: if recurse:
child.populate(True) child.populate(True)
self.children[name] = child 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): def get_child_target(self, name):
# return a new target for a named subdirectory of this dir # return a new target for a named subdirectory of this dir
@ -407,9 +415,11 @@ class TahoeDirectoryTarget:
set_data = {} set_data = {}
for (name, filecap) in self.new_children.items(): for (name, filecap) in self.new_children.items():
# it just so happens that ?t=set_children will accept both file # 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 # correctly. So don't bother trying to figure out whether the one
# we have is read-only or read-write. # 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}] set_data[name] = ["filenode", {"rw_uri": filecap}]
body = simplejson.dumps(set_data) body = simplejson.dumps(set_data)
POST(url, body) POST(url, body)
@ -770,6 +780,7 @@ def copy(options):
# local-file-in-the-way # local-file-in-the-way
# touch proposed # touch proposed
# tahoe cp -r my:docs/proposed/denver.txt proposed/denver.txt # tahoe cp -r my:docs/proposed/denver.txt proposed/denver.txt
# handling of unknown nodes
# things that maybe should be errors but aren't # things that maybe should be errors but aren't
# local-dir-in-the-way # local-dir-in-the-way

View File

@ -40,6 +40,7 @@ def put(options):
# DIRCAP:./subdir/foo : DIRCAP/subdir/foo # DIRCAP:./subdir/foo : DIRCAP/subdir/foo
# MUTABLE-FILE-WRITECAP : filecap # MUTABLE-FILE-WRITECAP : filecap
# FIXME: this shouldn't rely on a particular prefix.
if to_file.startswith("URI:SSK:"): if to_file.startswith("URI:SSK:"):
url = nodeurl + "uri/%s" % urllib.quote(to_file) url = nodeurl + "uri/%s" % urllib.quote(to_file)
else: else:

View File

@ -51,6 +51,8 @@ class FakeCHKFileNode:
def get_uri(self): def get_uri(self):
return self.my_uri.to_string() return self.my_uri.to_string()
def get_write_uri(self):
return None
def get_readonly_uri(self): def get_readonly_uri(self):
return self.my_uri.to_string() return self.my_uri.to_string()
def get_cap(self): def get_cap(self):
@ -103,6 +105,12 @@ class FakeCHKFileNode:
return False return False
def is_readonly(self): def is_readonly(self):
return True 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): def get_size(self):
try: try:
@ -190,6 +198,10 @@ class FakeMutableFileNode:
return self.my_uri.get_readonly() return self.my_uri.get_readonly()
def get_uri(self): def get_uri(self):
return self.my_uri.to_string() 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): def get_readonly(self):
return self.my_uri.get_readonly() return self.my_uri.get_readonly()
def get_readonly_uri(self): def get_readonly_uri(self):
@ -200,6 +212,12 @@ class FakeMutableFileNode:
return self.my_uri.is_readonly() return self.my_uri.is_readonly()
def is_mutable(self): def is_mutable(self):
return self.my_uri.is_mutable() 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): def get_writekey(self):
return "\x00"*16 return "\x00"*16
def get_size(self): def get_size(self):

View File

@ -288,11 +288,15 @@ class NodeMaker(unittest.TestCase):
self.failUnless(n.is_readonly()) self.failUnless(n.is_readonly())
self.failUnless(n.is_mutable()) self.failUnless(n.is_mutable())
future = "x-tahoe-crazy://future_cap_format." unknown_rw = "lafs://from_the_future"
n = c.create_node_from_uri(future) unknown_ro = "lafs://readonly_from_the_future"
n = c.create_node_from_uri(unknown_rw, unknown_ro)
self.failUnless(IFilesystemNode.providedBy(n)) self.failUnless(IFilesystemNode.providedBy(n))
self.failIf(IFileNode.providedBy(n)) self.failIf(IFileNode.providedBy(n))
self.failIf(IImmutableFileNode.providedBy(n)) self.failIf(IImmutableFileNode.providedBy(n))
self.failIf(IMutableFileNode.providedBy(n)) self.failIf(IMutableFileNode.providedBy(n))
self.failIf(IDirectoryNode.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)

View File

@ -7,8 +7,9 @@ from allmydata import uri, dirnode
from allmydata.client import Client from allmydata.client import Client
from allmydata.immutable import upload from allmydata.immutable import upload
from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \ from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \
ExistingChildError, NoSuchChildError, NotDeepImmutableError, \ ExistingChildError, NoSuchChildError, MustNotBeUnknownRWError, \
IDeepCheckResults, IDeepCheckAndRepairResults, CannotPackUnknownNodeError MustBeDeepImmutableError, MustBeReadonlyError, \
IDeepCheckResults, IDeepCheckAndRepairResults
from allmydata.mutable.filenode import MutableFileNode from allmydata.mutable.filenode import MutableFileNode
from allmydata.mutable.common import UncoordinatedWriteError from allmydata.mutable.common import UncoordinatedWriteError
from allmydata.util import hashutil, base32 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, \ from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \
ErrorMixin ErrorMixin
from allmydata.test.no_network import GridTestMixin 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 allmydata.nodemaker import NodeMaker
from base64 import b32decode from base64 import b32decode
import common_util as testutil import common_util as testutil
@ -32,6 +33,11 @@ class Dirnode(GridTestMixin, unittest.TestCase,
d = c.create_dirnode() d = c.create_dirnode()
def _done(res): def _done(res):
self.failUnless(isinstance(res, dirnode.DirectoryNode)) 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) rep = str(res)
self.failUnless("RW-MUT" in rep) self.failUnless("RW-MUT" in rep)
d.addCallback(_done) d.addCallback(_done)
@ -44,36 +50,74 @@ class Dirnode(GridTestMixin, unittest.TestCase,
nm = c.nodemaker nm = c.nodemaker
setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861" setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
one_uri = "URI:LIT:n5xgk" # LIT for "one" 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), {}), kids = {u"one": (nm.create_from_cap(one_uri), {}),
u"two": (nm.create_from_cap(setup_py_uri), u"two": (nm.create_from_cap(setup_py_uri),
{"metakey": "metavalue"}), {"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) d = c.create_dirnode(kids)
def _created(dn): def _created(dn):
self.failUnless(isinstance(dn, dirnode.DirectoryNode)) 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) rep = str(dn)
self.failUnless("RW-MUT" in rep) self.failUnless("RW-MUT" in rep)
return dn.list() return dn.list()
d.addCallback(_created) d.addCallback(_created)
def _check_kids(children): 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"] one_node, one_metadata = children[u"one"]
two_node, two_metadata = children[u"two"] 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(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.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(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(_check_kids)
d.addCallback(lambda ign: nm.create_new_mutable_directory(kids)) d.addCallback(lambda ign: nm.create_new_mutable_directory(kids))
d.addCallback(lambda dn: dn.list()) d.addCallback(lambda dn: dn.list())
d.addCallback(_check_kids) 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." bad_future_node = UnknownNode(future_write_uri, None)
future_node = UnknownNode(future_writecap, future_readcap) bad_kids1 = {u"one": (bad_future_node, {})}
bad_kids1 = {u"one": (future_node, {})}
d.addCallback(lambda ign: d.addCallback(lambda ign:
self.shouldFail(AssertionError, "bad_kids1", self.shouldFail(MustNotBeUnknownRWError, "bad_kids1",
"does not accept UnknownNode", "cannot attach unknown",
nm.create_new_mutable_directory, nm.create_new_mutable_directory,
bad_kids1)) bad_kids1))
bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)} bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)}
@ -91,17 +135,24 @@ class Dirnode(GridTestMixin, unittest.TestCase,
nm = c.nodemaker nm = c.nodemaker
setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861" setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
one_uri = "URI:LIT:n5xgk" # LIT for "one" one_uri = "URI:LIT:n5xgk" # LIT for "one"
mut_readcap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
mut_writecap = "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), {}), kids = {u"one": (nm.create_from_cap(one_uri), {}),
u"two": (nm.create_from_cap(setup_py_uri), u"two": (nm.create_from_cap(setup_py_uri),
{"metakey": "metavalue"}), {"metakey": "metavalue"}),
u"fut": (nm.create_from_cap(None, future_read_uri), {}),
} }
d = c.create_immutable_dirnode(kids) d = c.create_immutable_dirnode(kids)
def _created(dn): def _created(dn):
self.failUnless(isinstance(dn, dirnode.DirectoryNode)) self.failUnless(isinstance(dn, dirnode.DirectoryNode))
self.failIf(dn.is_mutable()) self.failIf(dn.is_mutable())
self.failUnless(dn.is_readonly()) self.failUnless(dn.is_readonly())
self.failIf(dn.is_unknown())
self.failUnless(dn.is_allowed_in_immutable_directory())
dn.raise_error()
rep = str(dn) rep = str(dn)
self.failUnless("RO-IMM" in rep) self.failUnless("RO-IMM" in rep)
cap = dn.get_cap() cap = dn.get_cap()
@ -109,50 +160,73 @@ class Dirnode(GridTestMixin, unittest.TestCase,
self.cap = cap self.cap = cap
return dn.list() return dn.list()
d.addCallback(_created) d.addCallback(_created)
def _check_kids(children): 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"] one_node, one_metadata = children[u"one"]
two_node, two_metadata = children[u"two"] two_node, two_metadata = children[u"two"]
fut_node, fut_metadata = children[u"fut"]
self.failUnlessEqual(one_node.get_size(), 3) 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.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(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(_check_kids)
d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string())) d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
d.addCallback(lambda dn: dn.list()) d.addCallback(lambda dn: dn.list())
d.addCallback(_check_kids) 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." bad_future_node1 = UnknownNode(future_write_uri, None)
future_node = UnknownNode(future_writecap, future_readcap) bad_kids1 = {u"one": (bad_future_node1, {})}
bad_kids1 = {u"one": (future_node, {})}
d.addCallback(lambda ign: d.addCallback(lambda ign:
self.shouldFail(AssertionError, "bad_kids1", self.shouldFail(MustNotBeUnknownRWError, "bad_kids1",
"does not accept UnknownNode", "cannot attach unknown",
c.create_immutable_dirnode, c.create_immutable_dirnode,
bad_kids1)) 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: d.addCallback(lambda ign:
self.shouldFail(AssertionError, "bad_kids2", self.shouldFail(MustBeDeepImmutableError, "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",
"is not immutable", "is not immutable",
c.create_immutable_dirnode, c.create_immutable_dirnode,
bad_kids3)) bad_kids2))
bad_kids4 = {u"one": (nm.create_from_cap(mut_readcap), {})} bad_kids3 = {u"one": (nm.create_from_cap(one_uri), None)}
d.addCallback(lambda ign: 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", "is not immutable",
c.create_immutable_dirnode, c.create_immutable_dirnode,
bad_kids4)) 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({})) d.addCallback(lambda ign: c.create_immutable_dirnode({}))
def _created_empty(dn): def _created_empty(dn):
self.failUnless(isinstance(dn, dirnode.DirectoryNode)) self.failUnless(isinstance(dn, dirnode.DirectoryNode))
self.failIf(dn.is_mutable()) self.failIf(dn.is_mutable())
self.failUnless(dn.is_readonly()) self.failUnless(dn.is_readonly())
self.failIf(dn.is_unknown())
self.failUnless(dn.is_allowed_in_immutable_directory())
dn.raise_error()
rep = str(dn) rep = str(dn)
self.failUnless("RO-IMM" in rep) self.failUnless("RO-IMM" in rep)
cap = dn.get_cap() cap = dn.get_cap()
@ -168,6 +242,9 @@ class Dirnode(GridTestMixin, unittest.TestCase,
self.failUnless(isinstance(dn, dirnode.DirectoryNode)) self.failUnless(isinstance(dn, dirnode.DirectoryNode))
self.failIf(dn.is_mutable()) self.failIf(dn.is_mutable())
self.failUnless(dn.is_readonly()) self.failUnless(dn.is_readonly())
self.failIf(dn.is_unknown())
self.failUnless(dn.is_allowed_in_immutable_directory())
dn.raise_error()
rep = str(dn) rep = str(dn)
self.failUnless("RO-IMM" in rep) self.failUnless("RO-IMM" in rep)
cap = dn.get_cap() cap = dn.get_cap()
@ -193,9 +270,9 @@ class Dirnode(GridTestMixin, unittest.TestCase,
d.addCallback(_check_kids) d.addCallback(_check_kids)
d.addCallback(lambda ign: n.get(u"subdir")) d.addCallback(lambda ign: n.get(u"subdir"))
d.addCallback(lambda sd: self.failIf(sd.is_mutable())) 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: d.addCallback(lambda ign:
self.shouldFail(NotDeepImmutableError, "YZ", self.shouldFail(MustBeDeepImmutableError, "YZ",
"is not immutable", "is not immutable",
n.create_subdirectory, n.create_subdirectory,
u"sub2", bad_kids, mutable=False)) u"sub2", bad_kids, mutable=False))
@ -203,7 +280,6 @@ class Dirnode(GridTestMixin, unittest.TestCase,
d.addCallback(_made_parent) d.addCallback(_made_parent)
return d return d
def test_check(self): def test_check(self):
self.basedir = "dirnode/Dirnode/test_check" self.basedir = "dirnode/Dirnode/test_check"
self.set_up_grid() self.set_up_grid()
@ -337,24 +413,27 @@ class Dirnode(GridTestMixin, unittest.TestCase,
ro_dn = c.create_node_from_uri(ro_uri) ro_dn = c.create_node_from_uri(ro_uri)
self.failUnless(ro_dn.is_readonly()) self.failUnless(ro_dn.is_readonly())
self.failUnless(ro_dn.is_mutable()) 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) 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) 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) }) 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) 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") 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") 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", {}) 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) 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) rw_dn.move_child_to, u"child", ro_dn)
return ro_dn.list() return ro_dn.list()
d.addCallback(_ready) d.addCallback(_ready)
@ -901,8 +980,8 @@ class Packing(unittest.TestCase):
nodemaker = NodeMaker(None, None, None, nodemaker = NodeMaker(None, None, None,
None, None, None, None, None, None,
{"k": 3, "n": 10}, None) {"k": 3, "n": 10}, None)
writecap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" write_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
filenode = nodemaker.create_from_cap(writecap) filenode = nodemaker.create_from_cap(write_uri)
node = dirnode.DirectoryNode(filenode, nodemaker, None) node = dirnode.DirectoryNode(filenode, nodemaker, None)
children = node._unpack_contents(known_tree) children = node._unpack_contents(known_tree)
self._check_children(children) self._check_children(children)
@ -975,23 +1054,23 @@ class Packing(unittest.TestCase):
self.failUnlessIn("lit", packed) self.failUnlessIn("lit", packed)
kids = self._make_kids(nm, ["imm", "lit", "write"]) kids = self._make_kids(nm, ["imm", "lit", "write"])
self.failUnlessRaises(dirnode.MustBeDeepImmutable, self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
dirnode.pack_children, dirnode.pack_children,
fn, kids, deep_immutable=True) fn, kids, deep_immutable=True)
# read-only is not enough: all children must be immutable # read-only is not enough: all children must be immutable
kids = self._make_kids(nm, ["imm", "lit", "read"]) kids = self._make_kids(nm, ["imm", "lit", "read"])
self.failUnlessRaises(dirnode.MustBeDeepImmutable, self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
dirnode.pack_children, dirnode.pack_children,
fn, kids, deep_immutable=True) fn, kids, deep_immutable=True)
kids = self._make_kids(nm, ["imm", "lit", "dirwrite"]) kids = self._make_kids(nm, ["imm", "lit", "dirwrite"])
self.failUnlessRaises(dirnode.MustBeDeepImmutable, self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
dirnode.pack_children, dirnode.pack_children,
fn, kids, deep_immutable=True) fn, kids, deep_immutable=True)
kids = self._make_kids(nm, ["imm", "lit", "dirread"]) kids = self._make_kids(nm, ["imm", "lit", "dirread"])
self.failUnlessRaises(dirnode.MustBeDeepImmutable, self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
dirnode.pack_children, dirnode.pack_children,
fn, kids, deep_immutable=True) fn, kids, deep_immutable=True)
@ -1017,16 +1096,31 @@ class FakeMutableFile:
def get_cap(self): def get_cap(self):
return self.uri return self.uri
def get_uri(self): def get_uri(self):
return self.uri.to_string() return self.uri.to_string()
def get_write_uri(self):
return self.uri.to_string()
def download_best_version(self): def download_best_version(self):
return defer.succeed(self.data) return defer.succeed(self.data)
def get_writekey(self): def get_writekey(self):
return "writekey" return "writekey"
def is_readonly(self): def is_readonly(self):
return False return False
def is_mutable(self): def is_mutable(self):
return True return True
def is_unknown(self):
return False
def is_allowed_in_immutable_directory(self):
return False
def modify(self, modifier): def modify(self, modifier):
self.data = modifier(self.data, None, True) self.data = modifier(self.data, None, True)
return defer.succeed(None) return defer.succeed(None)
@ -1049,50 +1143,186 @@ class Dirnode2(unittest.TestCase, testutil.ShouldFailMixin):
self.nodemaker = client.nodemaker self.nodemaker = client.nodemaker
def test_from_future(self): def test_from_future(self):
# create a dirnode that contains unknown URI types, and make sure we # Create a mutable directory that contains unknown URI types, and make sure
# tolerate them properly. Since dirnodes aren't allowed to add # we tolerate them properly.
# unknown node types, we have to be tricky.
d = self.nodemaker.create_new_mutable_directory() d = self.nodemaker.create_new_mutable_directory()
future_writecap = "x-tahoe-crazy://I_am_from_the_future." future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
future_node = UnknownNode(future_writecap, future_readcap) future_imm_uri = "x-tahoe-crazy-immutable://I_am_from_the_future."
future_node = UnknownNode(future_write_uri, future_read_uri)
def _then(n): def _then(n):
self._node = n self._node = n
return n.set_node(u"future", future_node) return n.set_node(u"future", future_node)
d.addCallback(_then) d.addCallback(_then)
# we should be prohibited from adding an unknown URI to a directory, # 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 # just in the rw_uri slot, since we don't know how to diminish the cap
# dirnode's rocap slot), and we don't want to accidentally grant # to a readcap (for the ro_uri slot).
# write access to a holder of the dirnode's readcap.
d.addCallback(lambda ign: d.addCallback(lambda ign:
self.shouldFail(CannotPackUnknownNodeError, self.shouldFail(MustNotBeUnknownRWError,
"copy unknown", "copy unknown",
"cannot pack unknown node as child add", "cannot attach unknown rw cap as child",
self._node.set_uri, u"add", 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()) d.addCallback(lambda ign: self._node.list())
def _check(children): def _check(children):
self.failUnlessEqual(len(children), 1) self.failUnlessEqual(len(children), 4)
(fn, metadata) = children[u"future"] (fn, metadata) = children[u"future"]
self.failUnless(isinstance(fn, UnknownNode), fn) self.failUnless(isinstance(fn, UnknownNode), fn)
self.failUnlessEqual(fn.get_uri(), future_writecap) self.failUnlessEqual(fn.get_uri(), future_write_uri)
self.failUnlessEqual(fn.get_readonly_uri(), future_readcap) self.failUnlessEqual(fn.get_write_uri(), future_write_uri)
# but we *should* be allowed to copy this node, because the self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri)
# UnknownNode contains all the information that was in the
# original directory (readcap and writecap), so we're preserving (fn2, metadata2) = children[u"add-pair"]
# everything. 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) return self._node.set_node(u"copy", fn)
d.addCallback(_check) d.addCallback(_check)
d.addCallback(lambda ign: self._node.list()) d.addCallback(lambda ign: self._node.list())
def _check2(children): def _check2(children):
self.failUnlessEqual(len(children), 2) self.failUnlessEqual(len(children), 5)
(fn, metadata) = children[u"copy"] (fn, metadata) = children[u"copy"]
self.failUnless(isinstance(fn, UnknownNode), fn) self.failUnless(isinstance(fn, UnknownNode), fn)
self.failUnlessEqual(fn.get_uri(), future_writecap) self.failUnlessEqual(fn.get_uri(), future_write_uri)
self.failUnlessEqual(fn.get_readonly_uri(), future_readcap) self.failUnlessEqual(fn.get_write_uri(), future_write_uri)
self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri)
d.addCallback(_check2)
return d 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): class DeepStats(unittest.TestCase):
timeout = 240 # It takes longer than 120 seconds on Francois's arm box. timeout = 240 # It takes longer than 120 seconds on Francois's arm box.
def test_stats(self): def test_stats(self):

View File

@ -41,14 +41,21 @@ class Node(unittest.TestCase):
self.failUnlessEqual(fn1.get_readcap(), u) self.failUnlessEqual(fn1.get_readcap(), u)
self.failUnlessEqual(fn1.is_readonly(), True) self.failUnlessEqual(fn1.is_readonly(), True)
self.failUnlessEqual(fn1.is_mutable(), False) 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_readonly_uri(), u.to_string())
self.failUnlessEqual(fn1.get_size(), 1000) self.failUnlessEqual(fn1.get_size(), 1000)
self.failUnlessEqual(fn1.get_storage_index(), u.storage_index) self.failUnlessEqual(fn1.get_storage_index(), u.storage_index)
fn1.raise_error()
fn2.raise_error()
d = {} d = {}
d[fn1] = 1 # exercise __hash__ d[fn1] = 1 # exercise __hash__
v = fn1.get_verify_cap() v = fn1.get_verify_cap()
self.failUnless(isinstance(v, uri.CHKFileVerifierURI)) self.failUnless(isinstance(v, uri.CHKFileVerifierURI))
self.failUnlessEqual(fn1.get_repair_cap(), v) self.failUnlessEqual(fn1.get_repair_cap(), v)
self.failUnlessEqual(v.is_readonly(), True)
self.failUnlessEqual(v.is_mutable(), False)
def test_literal_filenode(self): def test_literal_filenode(self):
@ -64,9 +71,14 @@ class Node(unittest.TestCase):
self.failUnlessEqual(fn1.get_readcap(), u) self.failUnlessEqual(fn1.get_readcap(), u)
self.failUnlessEqual(fn1.is_readonly(), True) self.failUnlessEqual(fn1.is_readonly(), True)
self.failUnlessEqual(fn1.is_mutable(), False) 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_readonly_uri(), u.to_string())
self.failUnlessEqual(fn1.get_size(), len(DATA)) self.failUnlessEqual(fn1.get_size(), len(DATA))
self.failUnlessEqual(fn1.get_storage_index(), None) self.failUnlessEqual(fn1.get_storage_index(), None)
fn1.raise_error()
fn2.raise_error()
d = {} d = {}
d[fn1] = 1 # exercise __hash__ d[fn1] = 1 # exercise __hash__
@ -99,24 +111,29 @@ class Node(unittest.TestCase):
self.failUnlessEqual(n.get_writekey(), wk) self.failUnlessEqual(n.get_writekey(), wk)
self.failUnlessEqual(n.get_readkey(), rk) self.failUnlessEqual(n.get_readkey(), rk)
self.failUnlessEqual(n.get_storage_index(), si) 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 # happens they'll be None
self.failUnlessEqual(n.get_privkey(), None) self.failUnlessEqual(n.get_privkey(), None)
self.failUnlessEqual(n.get_encprivkey(), None) self.failUnlessEqual(n.get_encprivkey(), None)
self.failUnlessEqual(n.get_pubkey(), None) self.failUnlessEqual(n.get_pubkey(), None)
self.failUnlessEqual(n.get_uri(), u.to_string()) 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_readonly_uri(), u.get_readonly().to_string())
self.failUnlessEqual(n.get_cap(), u) self.failUnlessEqual(n.get_cap(), u)
self.failUnlessEqual(n.get_readcap(), u.get_readonly()) self.failUnlessEqual(n.get_readcap(), u.get_readonly())
self.failUnlessEqual(n.is_mutable(), True) self.failUnlessEqual(n.is_mutable(), True)
self.failUnlessEqual(n.is_readonly(), False) 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(), n2 = MutableFileNode(None, None, client.get_encoding_parameters(),
None).init_from_cap(u) None).init_from_cap(u)
self.failUnlessEqual(n, n2) self.failUnlessEqual(n, n2)
self.failIfEqual(n, "not even the right type") self.failIfEqual(n, "not even the right type")
self.failIfEqual(n, u) # not the right class self.failIfEqual(n, u) # not the right class
n.raise_error()
d = {n: "can these be used as dictionary keys?"} d = {n: "can these be used as dictionary keys?"}
d[n2] = "replace the old one" d[n2] = "replace the old one"
self.failUnlessEqual(len(d), 1) self.failUnlessEqual(len(d), 1)
@ -127,12 +144,16 @@ class Node(unittest.TestCase):
self.failUnlessEqual(nro.get_readonly(), nro) self.failUnlessEqual(nro.get_readonly(), nro)
self.failUnlessEqual(nro.get_cap(), u.get_readonly()) self.failUnlessEqual(nro.get_cap(), u.get_readonly())
self.failUnlessEqual(nro.get_readcap(), 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() nro_u = nro.get_uri()
self.failUnlessEqual(nro_u, nro.get_readonly_uri()) self.failUnlessEqual(nro_u, nro.get_readonly_uri())
self.failUnlessEqual(nro_u, u.get_readonly().to_string()) self.failUnlessEqual(nro_u, u.get_readonly().to_string())
self.failUnlessEqual(nro.is_mutable(), True) self.failUnlessEqual(nro.get_write_uri(), None)
self.failUnlessEqual(nro.is_readonly(), True)
self.failUnlessEqual(nro.get_repair_cap(), None) # RSAmut needs writecap self.failUnlessEqual(nro.get_repair_cap(), None) # RSAmut needs writecap
nro.raise_error()
v = n.get_verify_cap() v = n.get_verify_cap()
self.failUnless(isinstance(v, uri.SSKVerifierURI)) self.failUnless(isinstance(v, uri.SSKVerifierURI))

View File

@ -17,7 +17,7 @@ from allmydata.scripts import runner
from allmydata.interfaces import IDirectoryNode, IFileNode, \ from allmydata.interfaces import IDirectoryNode, IFileNode, \
NoSuchChildError, NoSharesError NoSuchChildError, NoSharesError
from allmydata.monitor import Monitor 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 allmydata.mutable import layout as mutable_layout
from foolscap.api import DeadReferenceError from foolscap.api import DeadReferenceError
from twisted.python.failure import Failure from twisted.python.failure import Failure
@ -890,11 +890,11 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
d1.addCallback(lambda res: dirnode.list()) d1.addCallback(lambda res: dirnode.list())
d1.addCallback(self.log, "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)") 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)") 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(self.log, "doing get(ro)")
d1.addCallback(lambda res: dirnode.get(u"mydata992")) d1.addCallback(lambda res: dirnode.get(u"mydata992"))
@ -902,17 +902,17 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
self.failUnless(IFileNode.providedBy(filenode))) self.failUnless(IFileNode.providedBy(filenode)))
d1.addCallback(self.log, "doing delete(ro)") 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")) d1.addCallback(lambda res: self.shouldFail2(NoSuchChildError, "get(missing)", "missing", dirnode.get, u"missing"))
personal = self._personal_node 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(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") d1.addCallback(self.log, "finished with _got_s2ro")
return d1 return d1

View File

@ -3,7 +3,7 @@ from twisted.trial import unittest
from allmydata import uri from allmydata import uri
from allmydata.util import hashutil, base32 from allmydata.util import hashutil, base32
from allmydata.interfaces import IURI, IFileURI, IDirnodeURI, IMutableFileURI, \ from allmydata.interfaces import IURI, IFileURI, IDirnodeURI, IMutableFileURI, \
IVerifierURI IVerifierURI, CapConstraintError
class Literal(unittest.TestCase): class Literal(unittest.TestCase):
def _help_test(self, data): def _help_test(self, data):
@ -22,8 +22,16 @@ class Literal(unittest.TestCase):
self.failIf(IDirnodeURI.providedBy(u2)) self.failIf(IDirnodeURI.providedBy(u2))
self.failUnlessEqual(u2.data, data) self.failUnlessEqual(u2.data, data)
self.failUnlessEqual(u2.get_size(), len(data)) self.failUnlessEqual(u2.get_size(), len(data))
self.failUnless(u.is_readonly()) self.failUnless(u2.is_readonly())
self.failIf(u.is_mutable()) 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() u3 = u.get_readonly()
self.failUnlessIdentical(u, u3) self.failUnlessIdentical(u, u3)
@ -51,18 +59,36 @@ class Compare(unittest.TestCase):
fileURI = 'URI:CHK:f5ahxa25t4qkktywz6teyfvcx4:opuioq7tj2y6idzfp6cazehtmgs5fdcebcz3cygrxyydvcozrmeq:3:10:345834' fileURI = 'URI:CHK:f5ahxa25t4qkktywz6teyfvcx4:opuioq7tj2y6idzfp6cazehtmgs5fdcebcz3cygrxyydvcozrmeq:3:10:345834'
chk1 = uri.CHKFileURI.init_from_string(fileURI) chk1 = uri.CHKFileURI.init_from_string(fileURI)
chk2 = 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.failIfEqual(lit1, chk1)
self.failUnlessEqual(chk1, chk2) self.failUnlessEqual(chk1, chk2)
self.failIfEqual(chk1, "not actually a URI") self.failIfEqual(chk1, "not actually a URI")
# these should be hashable too # these should be hashable too
s = set([lit1, chk1, chk2]) s = set([lit1, chk1, chk2, unk])
self.failUnlessEqual(len(s), 2) # since chk1==chk2 self.failUnlessEqual(len(s), 3) # since chk1==chk2
def test_is_uri(self): def test_is_uri(self):
lit1 = uri.LiteralFileURI("some data").to_string() lit1 = uri.LiteralFileURI("some data").to_string()
self.failUnless(uri.is_uri(lit1)) self.failUnless(uri.is_uri(lit1))
self.failIf(uri.is_uri(None)) 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): class CHKFile(unittest.TestCase):
def test_pack(self): def test_pack(self):
key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" 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.failUnless(IFileURI.providedBy(u))
self.failIf(IDirnodeURI.providedBy(u)) self.failIf(IDirnodeURI.providedBy(u))
self.failUnlessEqual(u.get_size(), 1234) self.failUnlessEqual(u.get_size(), 1234)
self.failUnless(u.is_readonly())
self.failIf(u.is_mutable())
u_ro = u.get_readonly() u_ro = u.get_readonly()
self.failUnlessIdentical(u, u_ro) self.failUnlessIdentical(u, u_ro)
he = u.to_human_encoding() he = u.to_human_encoding()
@ -109,11 +134,19 @@ class CHKFile(unittest.TestCase):
self.failUnless(IFileURI.providedBy(u2)) self.failUnless(IFileURI.providedBy(u2))
self.failIf(IDirnodeURI.providedBy(u2)) self.failIf(IDirnodeURI.providedBy(u2))
self.failUnlessEqual(u2.get_size(), 1234) 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() v = u.get_verify_cap()
self.failUnless(isinstance(v.to_string(), str)) self.failUnless(isinstance(v.to_string(), str))
self.failUnless(v.is_readonly())
self.failIf(v.is_mutable())
v2 = uri.from_string(v.to_string()) v2 = uri.from_string(v.to_string())
self.failUnlessEqual(v, v2) self.failUnlessEqual(v, v2)
he = v.to_human_encoding() he = v.to_human_encoding()
@ -126,6 +159,8 @@ class CHKFile(unittest.TestCase):
total_shares=10, total_shares=10,
size=1234) size=1234)
self.failUnless(isinstance(v3.to_string(), str)) self.failUnless(isinstance(v3.to_string(), str))
self.failUnless(v3.is_readonly())
self.failIf(v3.is_mutable())
def test_pack_badly(self): def test_pack_badly(self):
key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" 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"], self.failUnlessEqual(readable["UEB_hash"],
base32.b2a(hashutil.uri_extension_hash(ext))) base32.b2a(hashutil.uri_extension_hash(ext)))
class Invalid(unittest.TestCase): class Unknown(unittest.TestCase):
def test_from_future(self): def test_from_future(self):
# any URI type that we don't recognize should be treated as unknown # 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 " future_uri = "I am a URI from the future. Whatever you do, don't "
u = uri.from_string(future_uri) u = uri.from_string(future_uri)
self.failUnless(isinstance(u, uri.UnknownURI)) self.failUnless(isinstance(u, uri.UnknownURI))
self.failUnlessEqual(u.to_string(), future_uri) 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): class Constraint(unittest.TestCase):
def test_constraint(self): def test_constraint(self):
@ -226,6 +268,13 @@ class Mutable(unittest.TestCase):
self.failUnless(IMutableFileURI.providedBy(u2)) self.failUnless(IMutableFileURI.providedBy(u2))
self.failIf(IDirnodeURI.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() u3 = u2.get_readonly()
readkey = hashutil.ssk_readkey_hash(writekey) readkey = hashutil.ssk_readkey_hash(writekey)
self.failUnlessEqual(u3.fingerprint, fingerprint) self.failUnlessEqual(u3.fingerprint, fingerprint)
@ -236,6 +285,13 @@ class Mutable(unittest.TestCase):
self.failUnless(IMutableFileURI.providedBy(u3)) self.failUnless(IMutableFileURI.providedBy(u3))
self.failIf(IDirnodeURI.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() he = u3.to_human_encoding()
u3_h = uri.ReadonlySSKFileURI.init_from_human_encoding(he) u3_h = uri.ReadonlySSKFileURI.init_from_human_encoding(he)
self.failUnlessEqual(u3, u3_h) self.failUnlessEqual(u3, u3_h)
@ -249,6 +305,13 @@ class Mutable(unittest.TestCase):
self.failUnless(IMutableFileURI.providedBy(u4)) self.failUnless(IMutableFileURI.providedBy(u4))
self.failIf(IDirnodeURI.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()) u4a = uri.from_string(u4.to_string())
self.failUnlessEqual(u4a, u4) self.failUnlessEqual(u4a, u4)
self.failUnless("ReadonlySSKFileURI" in str(u4a)) self.failUnless("ReadonlySSKFileURI" in str(u4a))
@ -291,12 +354,19 @@ class Dirnode(unittest.TestCase):
self.failIf(IFileURI.providedBy(u2)) self.failIf(IFileURI.providedBy(u2))
self.failUnless(IDirnodeURI.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() u3 = u2.get_readonly()
self.failUnless(u3.is_readonly()) self.failUnless(u3.is_readonly())
self.failUnless(u3.is_mutable()) self.failUnless(u3.is_mutable())
self.failUnless(IURI.providedBy(u3)) self.failUnless(IURI.providedBy(u3))
self.failIf(IFileURI.providedBy(u3)) self.failIf(IFileURI.providedBy(u3))
self.failUnless(IDirnodeURI.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 u3n = u3._filenode_uri
self.failUnless(u3n.is_readonly()) self.failUnless(u3n.is_readonly())
self.failUnless(u3n.is_mutable()) self.failUnless(u3n.is_mutable())
@ -363,10 +433,16 @@ class Dirnode(unittest.TestCase):
self.failIf(IFileURI.providedBy(u2)) self.failIf(IFileURI.providedBy(u2))
self.failUnless(IDirnodeURI.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() u3 = u2.get_readonly()
self.failUnlessEqual(u3.to_string(), u2.to_string()) self.failUnlessEqual(u3.to_string(), u2.to_string())
self.failUnless(str(u3)) 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() u2_verifier = u2.get_verify_cap()
self.failUnless(isinstance(u2_verifier, self.failUnless(isinstance(u2_verifier,
uri.ImmutableDirectoryURIVerifier), uri.ImmutableDirectoryURIVerifier),

View File

@ -7,7 +7,7 @@ from twisted.internet import defer, reactor
from twisted.web import client, error, http from twisted.web import client, error, http
from twisted.python import failure, log from twisted.python import failure, log
from nevow import rend 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.shares import get_share_file
from allmydata.storage_client import StorageFarmBroker from allmydata.storage_client import StorageFarmBroker
from allmydata.immutable import upload, download 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.scripts.debug import CorruptShareOptions, corrupt_share
from allmydata.util import fileutil, base32 from allmydata.util import fileutil, base32
from allmydata.util.consumer import download_to_data from allmydata.util.consumer import download_to_data
from allmydata.util.netstring import split_netstring
from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \ from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
from allmydata.interfaces import IMutableFileNode 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) d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
# TODO: we lose the response code, so we can't check this # TODO: we lose the response code, so we can't check this
#self.failUnlessEqual(responsecode, 201) #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: d.addCallback(lambda res:
self.failUnlessChildContentsAre(self._foo_node, u"new.txt", self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
self.NEWFILE_CONTENTS)) self.NEWFILE_CONTENTS))
@ -746,7 +747,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
self.NEWFILE_CONTENTS) self.NEWFILE_CONTENTS)
# TODO: we lose the response code, so we can't check this # TODO: we lose the response code, so we can't check this
#self.failUnlessEqual(responsecode, 201) #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: d.addCallback(lambda res:
self.failUnlessChildContentsAre(self._foo_node, u"new.txt", self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
self.NEWFILE_CONTENTS)) self.NEWFILE_CONTENTS))
@ -776,7 +777,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
self.failIf(u.is_readonly()) self.failIf(u.is_readonly())
return res return res
d.addCallback(_check_uri) 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: d.addCallback(lambda res:
self.failUnlessMutableChildContentsAre(self._foo_node, self.failUnlessMutableChildContentsAre(self._foo_node,
u"new.txt", 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) d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
# TODO: we lose the response code, so we can't check this # TODO: we lose the response code, so we can't check this
#self.failUnlessEqual(responsecode, 200) #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: d.addCallback(lambda res:
self.failUnlessChildContentsAre(self._foo_node, u"bar.txt", self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
self.NEWFILE_CONTENTS)) self.NEWFILE_CONTENTS))
@ -821,7 +822,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
def test_PUT_NEWFILEURL_mkdirs(self): def test_PUT_NEWFILEURL_mkdirs(self):
d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS) d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
fn = self._foo_node 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.failIfNodeHasChild(fn, u"new.txt"))
d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir")) d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
d.addCallback(lambda res: d.addCallback(lambda res:
@ -954,7 +955,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
self.failUnless(re.search(get_sub, res), res) self.failUnless(re.search(get_sub, res), res)
d.addCallback(_check) d.addCallback(_check)
# look at a directory which is readonly # look at a readonly directory
d.addCallback(lambda res: d.addCallback(lambda res:
self.GET(self.public_url + "/reedownlee", followRedirect=True)) self.GET(self.public_url + "/reedownlee", followRedirect=True))
def _check2(res): def _check2(res):
@ -1167,23 +1168,33 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
return d return d
def test_POST_NEWDIRURL_initial_children(self): def test_POST_NEWDIRURL_initial_children(self):
(newkids, filecap1, filecap2, filecap3, (newkids, caps) = self._create_initial_children()
dircap) = self._create_initial_children()
d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children", d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
simplejson.dumps(newkids)) simplejson.dumps(newkids))
def _check(uri): def _check(uri):
n = self.s.create_node_from_uri(uri.strip()) n = self.s.create_node_from_uri(uri.strip())
d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign: d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-imm", filecap1)) self.failUnlessROChildURIIs(n, u"child-imm",
caps['filecap1']))
d2.addCallback(lambda ign: d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable", self.failUnlessRWChildURIIs(n, u"child-mutable",
filecap2)) caps['filecap2']))
d2.addCallback(lambda ign: d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable-ro", self.failUnlessROChildURIIs(n, u"child-mutable-ro",
filecap3)) caps['filecap3']))
d2.addCallback(lambda ign: 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 return d2
d.addCallback(_check) d.addCallback(_check)
d.addCallback(lambda res: 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(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir")) 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 return d
def test_POST_NEWDIRURL_immutable(self): 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", d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
simplejson.dumps(newkids)) simplejson.dumps(newkids))
def _check(uri): def _check(uri):
n = self.s.create_node_from_uri(uri.strip()) n = self.s.create_node_from_uri(uri.strip())
d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign: d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-imm", filecap1)) self.failUnlessROChildURIIs(n, u"child-imm",
caps['filecap1']))
d2.addCallback(lambda ign: d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"dirchild-imm", self.failUnlessROChildURIIs(n, u"unknownchild-imm",
immdircap)) caps['unknown_immcap']))
d2.addCallback(lambda ign:
self.failUnlessROChildURIIs(n, u"dirchild-imm",
caps['immdircap']))
return d2 return d2
d.addCallback(_check) d.addCallback(_check)
d.addCallback(lambda res: 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(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir")) 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(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) d.addErrback(self.explain_web_error)
return d return d
def test_POST_NEWDIRURL_immutable_bad(self): def test_POST_NEWDIRURL_immutable_bad(self):
(newkids, filecap1, filecap2, filecap3, (newkids, caps) = self._create_initial_children()
dircap) = self._create_initial_children()
d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad", d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
"400 Bad Request", "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.POST2,
self.public_url + "/foo/newdir?t=mkdir-immutable", self.public_url + "/foo/newdir?t=mkdir-immutable",
simplejson.dumps(newkids)) simplejson.dumps(newkids))
@ -1346,19 +1362,51 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(_check) d.addCallback(_check)
return d return d
def failUnlessChildURIIs(self, node, name, expected_uri): def failUnlessRWChildURIIs(self, node, name, expected_uri):
assert isinstance(name, unicode) assert isinstance(name, unicode)
d = node.get_child_at_path(name) d = node.get_child_at_path(name)
def _check(child): 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_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) d.addCallback(_check)
return d return d
def failUnlessURIMatchesChild(self, got_uri, node, name): def failUnlessROChildURIIs(self, node, name, expected_uri):
assert isinstance(name, unicode) assert isinstance(name, unicode)
d = node.get_child_at_path(name) d = node.get_child_at_path(name)
def _check(child): 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_uri())
self.failUnlessEqual(got_uri.strip(), child.get_readonly_uri())
d.addCallback(_check) d.addCallback(_check)
return d return d
@ -1369,7 +1417,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d = self.POST(self.public_url + "/foo", t="upload", d = self.POST(self.public_url + "/foo", t="upload",
file=("new.txt", self.NEWFILE_CONTENTS)) file=("new.txt", self.NEWFILE_CONTENTS))
fn = self._foo_node fn = self._foo_node
d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
d.addCallback(lambda res: d.addCallback(lambda res:
self.failUnlessChildContentsAre(fn, u"new.txt", self.failUnlessChildContentsAre(fn, u"new.txt",
self.NEWFILE_CONTENTS)) self.NEWFILE_CONTENTS))
@ -1380,7 +1428,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d = self.POST(self.public_url + "/foo", t="upload", d = self.POST(self.public_url + "/foo", t="upload",
file=(filename, self.NEWFILE_CONTENTS)) file=(filename, self.NEWFILE_CONTENTS))
fn = self._foo_node fn = self._foo_node
d.addCallback(self.failUnlessURIMatchesChild, fn, filename) d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
d.addCallback(lambda res: d.addCallback(lambda res:
self.failUnlessChildContentsAre(fn, filename, self.failUnlessChildContentsAre(fn, filename,
self.NEWFILE_CONTENTS)) self.NEWFILE_CONTENTS))
@ -1397,7 +1445,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
name=filename, name=filename,
file=("overridden", self.NEWFILE_CONTENTS)) file=("overridden", self.NEWFILE_CONTENTS))
fn = self._foo_node fn = self._foo_node
d.addCallback(self.failUnlessURIMatchesChild, fn, filename) d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
d.addCallback(lambda res: d.addCallback(lambda res:
self.failUnlessChildContentsAre(fn, filename, self.failUnlessChildContentsAre(fn, filename,
self.NEWFILE_CONTENTS)) 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", d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
file=("new.txt", self.NEWFILE_CONTENTS)) file=("new.txt", self.NEWFILE_CONTENTS))
fn = self._foo_node fn = self._foo_node
d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
d.addCallback(lambda res: d.addCallback(lambda res:
self.failUnlessMutableChildContentsAre(fn, u"new.txt", self.failUnlessMutableChildContentsAre(fn, u"new.txt",
self.NEWFILE_CONTENTS)) self.NEWFILE_CONTENTS))
@ -1518,7 +1566,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
self.POST(self.public_url + "/foo", t="upload", self.POST(self.public_url + "/foo", t="upload",
mutable="true", mutable="true",
file=("new.txt", NEWER_CONTENTS))) 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: d.addCallback(lambda res:
self.failUnlessMutableChildContentsAre(fn, u"new.txt", self.failUnlessMutableChildContentsAre(fn, u"new.txt",
NEWER_CONTENTS)) NEWER_CONTENTS))
@ -1534,7 +1582,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n" NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
d.addCallback(lambda res: d.addCallback(lambda res:
self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS)) 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: d.addCallback(lambda res:
self.failUnlessMutableChildContentsAre(fn, u"new.txt", self.failUnlessMutableChildContentsAre(fn, u"new.txt",
NEW2_CONTENTS)) NEW2_CONTENTS))
@ -1663,7 +1711,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d = self.POST(self.public_url + "/foo", t="upload", d = self.POST(self.public_url + "/foo", t="upload",
file=("bar.txt", self.NEWFILE_CONTENTS)) file=("bar.txt", self.NEWFILE_CONTENTS))
fn = self._foo_node fn = self._foo_node
d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt") d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
d.addCallback(lambda res: d.addCallback(lambda res:
self.failUnlessChildContentsAre(fn, u"bar.txt", self.failUnlessChildContentsAre(fn, u"bar.txt",
self.NEWFILE_CONTENTS)) self.NEWFILE_CONTENTS))
@ -1714,7 +1762,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
fn = self._foo_node fn = self._foo_node
d = self.POST(self.public_url + "/foo", t="upload", d = self.POST(self.public_url + "/foo", t="upload",
name="new.txt", file=self.NEWFILE_CONTENTS) 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: d.addCallback(lambda res:
self.failUnlessChildContentsAre(fn, u"new.txt", self.failUnlessChildContentsAre(fn, u"new.txt",
self.NEWFILE_CONTENTS)) self.NEWFILE_CONTENTS))
@ -1977,7 +2025,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
return d return d
def test_POST_mkdir_initial_children(self): 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 + d = self.POST2(self.public_url +
"/foo?t=mkdir-with-children&name=newdir", "/foo?t=mkdir-with-children&name=newdir",
simplejson.dumps(newkids)) 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(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir")) 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 return d
def test_POST_mkdir_immutable(self): 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 + d = self.POST2(self.public_url +
"/foo?t=mkdir-immutable&name=newdir", "/foo?t=mkdir-immutable&name=newdir",
simplejson.dumps(newkids)) 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(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir")) 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(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 return d
def test_POST_mkdir_immutable_bad(self): def test_POST_mkdir_immutable_bad(self):
(newkids, filecap1, filecap2, filecap3, (newkids, caps) = self._create_initial_children()
dircap) = self._create_initial_children()
d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad", d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
"400 Bad Request", "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.POST2,
self.public_url + self.public_url +
"/foo?t=mkdir-immutable&name=newdir", "/foo?t=mkdir-immutable&name=newdir",
@ -2068,21 +2117,43 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addErrback(self.explain_web_error) d.addErrback(self.explain_web_error)
return d 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): def _create_initial_children(self):
contents, n, filecap1 = self.makefile(12) contents, n, filecap1 = self.makefile(12)
md1 = {"metakey1": "metavalue1"} md1 = {"metakey1": "metavalue1"}
filecap2 = make_mutable_file_uri() filecap2 = make_mutable_file_uri()
node3 = self.s.create_node_from_uri(make_mutable_file_uri()) node3 = self.s.create_node_from_uri(make_mutable_file_uri())
filecap3 = node3.get_readonly_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()) node4 = self.s.create_node_from_uri(make_mutable_file_uri())
dircap = DirectoryNode(node4, None, None).get_uri() dircap = DirectoryNode(node4, None, None).get_uri()
newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1, newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
"metadata": md1, }], "ro_uri": self._make_readonly(filecap1),
u"child-mutable": ["filenode", {"rw_uri": filecap2}], "metadata": md1, }],
u"child-mutable": ["filenode", {"rw_uri": filecap2,
"ro_uri": self._make_readonly(filecap2)}],
u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}], 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): def _create_immutable_children(self):
contents, n, filecap1 = self.makefile(12) 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) tnode = create_chk_filenode("immutable directory contents\n"*10)
dnode = DirectoryNode(tnode, None, None) dnode = DirectoryNode(tnode, None, None)
assert not dnode.is_mutable() assert not dnode.is_mutable()
unknown_immcap = "imm.lafs://immutable_from_the_future"
immdircap = dnode.get_uri() immdircap = dnode.get_uri()
newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1, newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
"metadata": md1, }], "metadata": md1, }],
u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}], 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): def test_POST_mkdir_no_parentdir_initial_children(self):
(newkids, filecap1, filecap2, filecap3, (newkids, caps) = self._create_initial_children()
dircap) = self._create_initial_children()
d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids)) d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
def _after_mkdir(res): def _after_mkdir(res):
self.failUnless(res.startswith("URI:DIR"), res) self.failUnless(res.startswith("URI:DIR"), res)
n = self.s.create_node_from_uri(res) n = self.s.create_node_from_uri(res)
d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign: d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-imm", filecap1)) self.failUnlessROChildURIIs(n, u"child-imm",
caps['filecap1']))
d2.addCallback(lambda ign: d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable", self.failUnlessRWChildURIIs(n, u"child-mutable",
filecap2)) caps['filecap2']))
d2.addCallback(lambda ign: d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable-ro", self.failUnlessROChildURIIs(n, u"child-mutable-ro",
filecap3)) caps['filecap3']))
d2.addCallback(lambda ign: 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 return d2
d.addCallback(_after_mkdir) d.addCallback(_after_mkdir)
return d return d
@ -2122,8 +2207,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
def test_POST_mkdir_no_parentdir_unexpected_children(self): def test_POST_mkdir_no_parentdir_unexpected_children(self):
# the regular /uri?t=mkdir operation is specified to ignore its body. # the regular /uri?t=mkdir operation is specified to ignore its body.
# Only t=mkdir-with-children pays attention to it. # Only t=mkdir-with-children pays attention to it.
(newkids, filecap1, filecap2, filecap3, (newkids, caps) = self._create_initial_children()
dircap) = self._create_initial_children()
d = self.shouldHTTPError("POST t=mkdir unexpected children", d = self.shouldHTTPError("POST t=mkdir unexpected children",
400, "Bad Request", 400, "Bad Request",
"t=mkdir does not accept children=, " "t=mkdir does not accept children=, "
@ -2140,28 +2224,31 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
return d return d
def test_POST_mkdir_no_parentdir_immutable(self): 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)) d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
def _after_mkdir(res): def _after_mkdir(res):
self.failUnless(res.startswith("URI:DIR"), res) self.failUnless(res.startswith("URI:DIR"), res)
n = self.s.create_node_from_uri(res) n = self.s.create_node_from_uri(res)
d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign: d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-imm", filecap1)) self.failUnlessROChildURIIs(n, u"child-imm",
caps['filecap1']))
d2.addCallback(lambda ign: d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"dirchild-imm", self.failUnlessROChildURIIs(n, u"unknownchild-imm",
immdircap)) caps['unknown_immcap']))
d2.addCallback(lambda ign:
self.failUnlessROChildURIIs(n, u"dirchild-imm",
caps['immdircap']))
return d2 return d2
d.addCallback(_after_mkdir) d.addCallback(_after_mkdir)
return d return d
def test_POST_mkdir_no_parentdir_immutable_bad(self): def test_POST_mkdir_no_parentdir_immutable_bad(self):
(newkids, filecap1, filecap2, filecap3, (newkids, caps) = self._create_initial_children()
dircap) = self._create_initial_children()
d = self.shouldFail2(error.Error, d = self.shouldFail2(error.Error,
"test_POST_mkdir_no_parentdir_immutable_bad", "test_POST_mkdir_no_parentdir_immutable_bad",
"400 Bad Request", "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.POST2,
"/uri?t=mkdir-immutable", "/uri?t=mkdir-immutable",
simplejson.dumps(newkids)) simplejson.dumps(newkids))
@ -2269,9 +2356,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d = client.getPage(url, method="POST", postdata=reqbody) d = client.getPage(url, method="POST", postdata=reqbody)
def _then(res): def _then(res):
self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1") self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2") self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3") self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
d.addCallback(_then) d.addCallback(_then)
d.addErrback(self.dump_error) d.addErrback(self.dump_error)
@ -2283,7 +2370,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
def test_POST_put_uri(self): def test_POST_put_uri(self):
contents, n, newuri = self.makefile(8) contents, n, newuri = self.makefile(8)
d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri) 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: d.addCallback(lambda res:
self.failUnlessChildContentsAre(self._foo_node, u"new.txt", self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
contents)) contents))
@ -2292,7 +2379,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
def test_POST_put_uri_replace(self): def test_POST_put_uri_replace(self):
contents, n, newuri = self.makefile(8) contents, n, newuri = self.makefile(8)
d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri) 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: d.addCallback(lambda res:
self.failUnlessChildContentsAre(self._foo_node, u"bar.txt", self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
contents)) contents))
@ -2521,9 +2608,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(lambda res: d.addCallback(lambda res:
self.failUnlessEqual(res.strip(), new_uri)) self.failUnlessEqual(res.strip(), new_uri))
d.addCallback(lambda res: d.addCallback(lambda res:
self.failUnlessChildURIIs(self.public_root, self.failUnlessRWChildURIIs(self.public_root,
u"foo", u"foo",
new_uri)) new_uri))
return d return d
d.addCallback(_made_dir) d.addCallback(_made_dir)
return d return d
@ -2540,9 +2627,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
self.public_url + "/foo?t=uri&replace=false", self.public_url + "/foo?t=uri&replace=false",
new_uri) new_uri)
d.addCallback(lambda res: d.addCallback(lambda res:
self.failUnlessChildURIIs(self.public_root, self.failUnlessRWChildURIIs(self.public_root,
u"foo", u"foo",
self._foo_uri)) self._foo_uri))
return d return d
d.addCallback(_made_dir) d.addCallback(_made_dir)
return d return d
@ -2552,9 +2639,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
"400 Bad Request", "PUT to a directory", "400 Bad Request", "PUT to a directory",
self.PUT, self.public_url + "/foo?t=BOGUS", "") self.PUT, self.public_url + "/foo?t=BOGUS", "")
d.addCallback(lambda res: d.addCallback(lambda res:
self.failUnlessChildURIIs(self.public_root, self.failUnlessRWChildURIIs(self.public_root,
u"foo", u"foo",
self._foo_uri)) self._foo_uri))
return d return d
def test_PUT_NEWFILEURL_uri(self): def test_PUT_NEWFILEURL_uri(self):
@ -3081,71 +3168,246 @@ class Grid(GridTestMixin, WebErrorMixin, unittest.TestCase, ShouldFailMixin):
d.addErrback(self.explain_web_error) d.addErrback(self.explain_web_error)
return d return d
def test_unknown(self): def test_unknown(self, immutable=False):
self.basedir = "web/Grid/unknown" self.basedir = "web/Grid/unknown"
if immutable:
self.basedir = "web/Grid/unknown-immutable"
self.set_up_grid() self.set_up_grid()
c0 = self.g.clients[0] c0 = self.g.clients[0]
self.uris = {} self.uris = {}
self.fileurls = {} self.fileurls = {}
future_writecap = "x-tahoe-crazy://I_am_from_the_future." future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
future_readcap = "x-tahoe-crazy-readonly://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 # 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="") 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): def _stash_root_and_create_file(n):
self.rootnode = n self.rootnode = n
self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/" self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
self.rourl = "uri/" + urllib.quote(n.get_readonly_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) d.addCallback(_stash_root_and_create_file)
# make sure directory listing tolerates unknown nodes # make sure directory listing tolerates unknown nodes
d.addCallback(lambda ign: self.GET(self.rooturl)) d.addCallback(lambda ign: self.GET(self.rooturl))
def _check_html(res): def _check_directory_html(res):
self.failUnlessIn("<td>future</td>", res) self.failUnlessIn("<td>%s</td>" % (str(name),), res)
# find the More Info link for "future", should be relative # find the More Info link for name, should be relative
mo = re.search(r'<a href="([^"]+)">More Info</a>', res) mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
info_url = mo.group(1) 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")) 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) data = simplejson.loads(res)
self.failUnlessEqual(data[0], "dirnode") self.failUnlessEqual(data[0], "dirnode")
f = data[1]["children"]["future"] f = data[1]["children"][name]
self.failUnlessEqual(f[0], "unknown") self.failUnlessEqual(f[0], "unknown")
if expect_writecap: if expect_rw_uri:
self.failUnlessEqual(f[1]["rw_uri"], future_writecap) self.failUnlessEqual(f[1]["rw_uri"], future_write_uri)
else: else:
self.failIfIn("rw_uri", f[1]) 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]) self.failUnless("metadata" in f[1])
d.addCallback(_check_json, expect_writecap=True) d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
d.addCallback(lambda ign: self.GET(expected_info_url))
def _check_info(res, expect_readcap): def _check_info(res, expect_rw_uri, expect_ro_uri):
self.failUnlessIn("Object Type: <span>unknown</span>", res) self.failUnlessIn("Object Type: <span>unknown</span>", res)
self.failUnlessIn(future_writecap, res) if expect_rw_uri:
if expect_readcap: self.failUnlessIn(future_write_uri, res)
self.failUnlessIn(future_readcap, 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("Raw data as", res)
self.failIfIn("Directory writecap", res) self.failIfIn("Directory writecap", res)
self.failIfIn("Checker Operations", res) self.failIfIn("Checker Operations", res)
self.failIfIn("Mutable File Operations", res) self.failIfIn("Mutable File Operations", res)
self.failIfIn("Directory 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")) # FIXME: these should have expect_rw_uri=not immutable; I don't know
d.addCallback(_check_info, expect_readcap=True) # 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 # 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(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(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 return d
def test_deep_check(self): 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 # this tests that deep-check and stream-manifest will ignore
# UnknownNode instances. Hopefully this will also cover deep-stats. # UnknownNode instances. Hopefully this will also cover deep-stats.
future_writecap = "x-tahoe-crazy://I_am_from_the_future." future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
future_node = UnknownNode(future_writecap, future_readcap) future_node = UnknownNode(future_write_uri, future_read_uri)
d.addCallback(lambda ign: self.rootnode.set_node(u"future",future_node)) d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
def _clobber_shares(ignored): def _clobber_shares(ignored):
self.delete_shares_numbered(self.uris["sick"], [0,1]) self.delete_shares_numbered(self.uris["sick"], [0,1])

View File

@ -1,29 +1,181 @@
from zope.interface import implements from zope.interface import implements
from twisted.internet import defer 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: class UnknownNode:
implements(IFilesystemNode) implements(IFilesystemNode)
def __init__(self, writecap, readcap):
assert writecap is None or isinstance(writecap, str) def __init__(self, given_rw_uri, given_ro_uri, deep_immutable=False,
self.writecap = writecap name=u"<unknown name>"):
assert readcap is None or isinstance(readcap, str) assert given_rw_uri is None or isinstance(given_rw_uri, str)
self.readcap = readcap 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): 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): def get_readonly_uri(self):
return self.readcap return self.ro_uri
def get_storage_index(self): def get_storage_index(self):
return None return None
def get_verify_cap(self): def get_verify_cap(self):
return None return None
def get_repair_cap(self): def get_repair_cap(self):
return None return None
def get_size(self): def get_size(self):
return None return None
def get_current_size(self): def get_current_size(self):
return defer.succeed(None) return defer.succeed(None)
def check(self, monitor, verify, add_lease): def check(self, monitor, verify, add_lease):
return defer.succeed(None) return defer.succeed(None)
def check_and_repair(self, monitor, verify, add_lease): def check_and_repair(self, monitor, verify, add_lease):
return defer.succeed(None) return defer.succeed(None)

View File

@ -5,9 +5,10 @@ from twisted.python.components import registerAdapter
from allmydata.storage.server import si_a2b, si_b2a from allmydata.storage.server import si_a2b, si_b2a
from allmydata.util import base32, hashutil from allmydata.util import base32, hashutil
from allmydata.interfaces import IURI, IDirnodeURI, IFileURI, IImmutableFileURI, \ 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 pass
# the URI shall be an ascii representation of the file. It shall contain # 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): def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(uri) mo = cls.HUMAN_RE.search(uri)
if not mo: 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)), return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
int(mo.group(3)), int(mo.group(4)), int(mo.group(5))) 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): def init_from_string(cls, uri):
mo = cls.STRING_RE.search(uri) mo = cls.STRING_RE.search(uri)
if not mo: 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)), return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
int(mo.group(3)), int(mo.group(4)), int(mo.group(5))) int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
@ -97,8 +98,10 @@ class CHKFileURI(_BaseURI):
def is_readonly(self): def is_readonly(self):
return True return True
def is_mutable(self): def is_mutable(self):
return False return False
def get_readonly(self): def get_readonly(self):
return self return self
@ -134,14 +137,16 @@ class CHKFileVerifierURI(_BaseURI):
@classmethod @classmethod
def init_from_human_encoding(cls, uri): def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(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)), return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
int(mo.group(3)), int(mo.group(4)), int(mo.group(5))) int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
@classmethod @classmethod
def init_from_string(cls, uri): def init_from_string(cls, uri):
mo = cls.STRING_RE.search(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)), return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)),
int(mo.group(3)), int(mo.group(4)), int(mo.group(5))) int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
@ -157,6 +162,18 @@ class CHKFileVerifierURI(_BaseURI):
self.total_shares, self.total_shares,
self.size)) 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): class LiteralFileURI(_BaseURI):
implements(IURI, IImmutableFileURI) implements(IURI, IImmutableFileURI)
@ -173,13 +190,15 @@ class LiteralFileURI(_BaseURI):
@classmethod @classmethod
def init_from_human_encoding(cls, uri): def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(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))) return cls(base32.a2b(mo.group(1)))
@classmethod @classmethod
def init_from_string(cls, uri): def init_from_string(cls, uri):
mo = cls.STRING_RE.search(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))) return cls(base32.a2b(mo.group(1)))
def to_string(self): def to_string(self):
@ -187,10 +206,13 @@ class LiteralFileURI(_BaseURI):
def is_readonly(self): def is_readonly(self):
return True return True
def is_mutable(self): def is_mutable(self):
return False return False
def get_readonly(self): def get_readonly(self):
return self return self
def get_storage_index(self): def get_storage_index(self):
return None return None
@ -201,6 +223,7 @@ class LiteralFileURI(_BaseURI):
def get_size(self): def get_size(self):
return len(self.data) return len(self.data)
class WriteableSSKFileURI(_BaseURI): class WriteableSSKFileURI(_BaseURI):
implements(IURI, IMutableFileURI) implements(IURI, IMutableFileURI)
@ -221,7 +244,7 @@ class WriteableSSKFileURI(_BaseURI):
def init_from_human_encoding(cls, uri): def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(uri) mo = cls.HUMAN_RE.search(uri)
if not mo: 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))) return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
@classmethod @classmethod
@ -242,18 +265,23 @@ class WriteableSSKFileURI(_BaseURI):
def abbrev(self): def abbrev(self):
return base32.b2a(self.writekey[:5]) return base32.b2a(self.writekey[:5])
def abbrev_si(self): def abbrev_si(self):
return base32.b2a(self.storage_index)[:5] return base32.b2a(self.storage_index)[:5]
def is_readonly(self): def is_readonly(self):
return False return False
def is_mutable(self): def is_mutable(self):
return True return True
def get_readonly(self): def get_readonly(self):
return ReadonlySSKFileURI(self.readkey, self.fingerprint) return ReadonlySSKFileURI(self.readkey, self.fingerprint)
def get_verify_cap(self): def get_verify_cap(self):
return SSKVerifierURI(self.storage_index, self.fingerprint) return SSKVerifierURI(self.storage_index, self.fingerprint)
class ReadonlySSKFileURI(_BaseURI): class ReadonlySSKFileURI(_BaseURI):
implements(IURI, IMutableFileURI) implements(IURI, IMutableFileURI)
@ -271,14 +299,14 @@ class ReadonlySSKFileURI(_BaseURI):
def init_from_human_encoding(cls, uri): def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(uri) mo = cls.HUMAN_RE.search(uri)
if not mo: 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))) return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
@classmethod @classmethod
def init_from_string(cls, uri): def init_from_string(cls, uri):
mo = cls.STRING_RE.search(uri) mo = cls.STRING_RE.search(uri)
if not mo: 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))) return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
def to_string(self): def to_string(self):
@ -292,18 +320,23 @@ class ReadonlySSKFileURI(_BaseURI):
def abbrev(self): def abbrev(self):
return base32.b2a(self.readkey[:5]) return base32.b2a(self.readkey[:5])
def abbrev_si(self): def abbrev_si(self):
return base32.b2a(self.storage_index)[:5] return base32.b2a(self.storage_index)[:5]
def is_readonly(self): def is_readonly(self):
return True return True
def is_mutable(self): def is_mutable(self):
return True return True
def get_readonly(self): def get_readonly(self):
return self return self
def get_verify_cap(self): def get_verify_cap(self):
return SSKVerifierURI(self.storage_index, self.fingerprint) return SSKVerifierURI(self.storage_index, self.fingerprint)
class SSKVerifierURI(_BaseURI): class SSKVerifierURI(_BaseURI):
implements(IVerifierURI) implements(IVerifierURI)
@ -319,13 +352,15 @@ class SSKVerifierURI(_BaseURI):
@classmethod @classmethod
def init_from_human_encoding(cls, uri): def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(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))) return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
@classmethod @classmethod
def init_from_string(cls, uri): def init_from_string(cls, uri):
mo = cls.STRING_RE.search(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))) return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
def to_string(self): def to_string(self):
@ -334,6 +369,18 @@ class SSKVerifierURI(_BaseURI):
return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index), return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index),
base32.b2a(self.fingerprint)) 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): class _DirectoryBaseURI(_BaseURI):
implements(IURI, IDirnodeURI) implements(IURI, IDirnodeURI)
def __init__(self, filenode_uri=None): def __init__(self, filenode_uri=None):
@ -373,15 +420,16 @@ class _DirectoryBaseURI(_BaseURI):
def abbrev(self): def abbrev(self):
return self._filenode_uri.to_string().split(':')[2][:5] return self._filenode_uri.to_string().split(':')[2][:5]
def abbrev_si(self): def abbrev_si(self):
return base32.b2a(self._filenode_uri.storage_index)[:5] return base32.b2a(self._filenode_uri.storage_index)[:5]
def get_filenode_cap(self):
return self._filenode_uri
def is_mutable(self): def is_mutable(self):
return True return True
def get_filenode_cap(self):
return self._filenode_uri
def get_verify_cap(self): def get_verify_cap(self):
return DirectoryURIVerifier(self._filenode_uri.get_verify_cap()) return DirectoryURIVerifier(self._filenode_uri.get_verify_cap())
@ -407,6 +455,7 @@ class DirectoryURI(_DirectoryBaseURI):
def get_readonly(self): def get_readonly(self):
return ReadonlyDirectoryURI(self._filenode_uri.get_readonly()) return ReadonlyDirectoryURI(self._filenode_uri.get_readonly())
class ReadonlyDirectoryURI(_DirectoryBaseURI): class ReadonlyDirectoryURI(_DirectoryBaseURI):
implements(IReadonlyDirectoryURI) implements(IReadonlyDirectoryURI)
@ -426,26 +475,30 @@ class ReadonlyDirectoryURI(_DirectoryBaseURI):
def get_readonly(self): def get_readonly(self):
return self return self
class _ImmutableDirectoryBaseURI(_DirectoryBaseURI): class _ImmutableDirectoryBaseURI(_DirectoryBaseURI):
def __init__(self, filenode_uri=None): def __init__(self, filenode_uri=None):
if filenode_uri: if filenode_uri:
assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri
assert not filenode_uri.is_mutable()
_DirectoryBaseURI.__init__(self, filenode_uri) _DirectoryBaseURI.__init__(self, filenode_uri)
def is_mutable(self):
return False
def is_readonly(self): def is_readonly(self):
return True return True
def is_mutable(self):
return False
def get_readonly(self): def get_readonly(self):
return self return self
class ImmutableDirectoryURI(_ImmutableDirectoryBaseURI): class ImmutableDirectoryURI(_ImmutableDirectoryBaseURI):
BASE_STRING='URI:DIR2-CHK:' BASE_STRING='URI:DIR2-CHK:'
BASE_STRING_RE=re.compile('^'+BASE_STRING) BASE_STRING_RE=re.compile('^'+BASE_STRING)
BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK'+SEP) BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK'+SEP)
INNER_URI_CLASS=CHKFileURI INNER_URI_CLASS=CHKFileURI
def get_verify_cap(self): def get_verify_cap(self):
vcap = self._filenode_uri.get_verify_cap() vcap = self._filenode_uri.get_verify_cap()
return ImmutableDirectoryURIVerifier(vcap) return ImmutableDirectoryURIVerifier(vcap)
@ -456,10 +509,12 @@ class LiteralDirectoryURI(_ImmutableDirectoryBaseURI):
BASE_STRING_RE=re.compile('^'+BASE_STRING) BASE_STRING_RE=re.compile('^'+BASE_STRING)
BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-LIT'+SEP) BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-LIT'+SEP)
INNER_URI_CLASS=LiteralFileURI INNER_URI_CLASS=LiteralFileURI
def get_verify_cap(self): def get_verify_cap(self):
# LIT caps have no verifier, since they aren't distributed # LIT caps have no verifier, since they aren't distributed
return None return None
def wrap_dirnode_cap(filecap): def wrap_dirnode_cap(filecap):
if isinstance(filecap, WriteableSSKFileURI): if isinstance(filecap, WriteableSSKFileURI):
return DirectoryURI(filecap) return DirectoryURI(filecap)
@ -469,7 +524,8 @@ def wrap_dirnode_cap(filecap):
return ImmutableDirectoryURI(filecap) return ImmutableDirectoryURI(filecap)
if isinstance(filecap, LiteralFileURI): if isinstance(filecap, LiteralFileURI):
return LiteralDirectoryURI(filecap) 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): class DirectoryURIVerifier(_DirectoryBaseURI):
implements(IVerifierURI) implements(IVerifierURI)
@ -487,6 +543,10 @@ class DirectoryURIVerifier(_DirectoryBaseURI):
def get_filenode_cap(self): def get_filenode_cap(self):
return self._filenode_uri return self._filenode_uri
def is_mutable(self):
return False
class ImmutableDirectoryURIVerifier(DirectoryURIVerifier): class ImmutableDirectoryURIVerifier(DirectoryURIVerifier):
implements(IVerifierURI) implements(IVerifierURI)
BASE_STRING='URI:DIR2-CHK-Verifier:' 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) BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK-VERIFIER'+SEP)
INNER_URI_CLASS=CHKFileVerifierURI INNER_URI_CLASS=CHKFileVerifierURI
class UnknownURI: class UnknownURI:
def __init__(self, uri): def __init__(self, uri, error=None):
self._uri = uri self._uri = uri
self._error = error
def to_string(self): def to_string(self):
return self._uri return self._uri
def from_string(s): def get_readonly(self):
if not isinstance(s, str): return None
raise TypeError("unknown URI type: %s.." % str(s)[:100])
elif s.startswith('URI:CHK:'): def get_error(self):
return CHKFileURI.init_from_string(s) return self._error
elif s.startswith('URI:CHK-Verifier:'):
return CHKFileVerifierURI.init_from_string(s) def get_verify_cap(self):
elif s.startswith('URI:LIT:'): return None
return LiteralFileURI.init_from_string(s)
elif s.startswith('URI:SSK:'):
return WriteableSSKFileURI.init_from_string(s) ALLEGED_READONLY_PREFIX = 'ro.'
elif s.startswith('URI:SSK-RO:'): ALLEGED_IMMUTABLE_PREFIX = 'imm.'
return ReadonlySSKFileURI.init_from_string(s)
elif s.startswith('URI:SSK-Verifier:'): def from_string(u, deep_immutable=False, name=u"<unknown name>"):
return SSKVerifierURI.init_from_string(s) if not isinstance(u, str):
elif s.startswith('URI:DIR2:'): raise TypeError("unknown URI type: %s.." % str(u)[:100])
return DirectoryURI.init_from_string(s)
elif s.startswith('URI:DIR2-RO:'): # We allow and check ALLEGED_READONLY_PREFIX or ALLEGED_IMMUTABLE_PREFIX
return ReadonlyDirectoryURI.init_from_string(s) # on all URIs, even though we would only strictly need to do so for caps of
elif s.startswith('URI:DIR2-Verifier:'): # new formats (post Tahoe-LAFS 1.6). URIs that are not consistent with their
return DirectoryURIVerifier.init_from_string(s) # prefix are treated as unknown. This should be revisited when we add the
elif s.startswith('URI:DIR2-CHK:'): # new cap formats. See <http://allmydata.org/trac/tahoe/ticket/833#comment:31>.
return ImmutableDirectoryURI.init_from_string(s) s = u
elif s.startswith('URI:DIR2-LIT:'): can_be_mutable = can_be_writeable = not deep_immutable
return LiteralDirectoryURI.init_from_string(s) if s.startswith(ALLEGED_IMMUTABLE_PREFIX):
return UnknownURI(s) 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): def is_uri(s):
try: try:
from_string(s) from_string(s, deep_immutable=False)
return True return True
except (TypeError, AssertionError): except (TypeError, AssertionError):
return False return False
def from_string_dirnode(s): def is_literal_file_uri(s):
u = from_string(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) assert IDirnodeURI.providedBy(u)
return u return u
registerAdapter(from_string_dirnode, str, IDirnodeURI) registerAdapter(from_string_dirnode, str, IDirnodeURI)
def from_string_filenode(s): def from_string_filenode(s, **kwargs):
u = from_string(s) u = from_string(s, **kwargs)
assert IFileURI.providedBy(u) assert IFileURI.providedBy(u)
return u return u
registerAdapter(from_string_filenode, str, IFileURI) registerAdapter(from_string_filenode, str, IFileURI)
def from_string_mutable_filenode(s): def from_string_mutable_filenode(s, **kwargs):
u = from_string(s) u = from_string(s, **kwargs)
assert IMutableFileURI.providedBy(u) assert IMutableFileURI.providedBy(u)
return u return u
registerAdapter(from_string_mutable_filenode, str, IMutableFileURI) registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
def from_string_verifier(s): def from_string_verifier(s, **kwargs):
u = from_string(s) u = from_string(s, **kwargs)
assert IVerifierURI.providedBy(u) assert IVerifierURI.providedBy(u)
return u return u
registerAdapter(from_string_verifier, str, IVerifierURI) registerAdapter(from_string_verifier, str, IVerifierURI)

View File

@ -8,7 +8,8 @@ from nevow.inevow import IRequest
from nevow.util import resource_filename from nevow.util import resource_filename
from allmydata.interfaces import ExistingChildError, NoSuchChildError, \ from allmydata.interfaces import ExistingChildError, NoSuchChildError, \
FileTooLargeError, NotEnoughSharesError, NoSharesError, \ FileTooLargeError, NotEnoughSharesError, NoSharesError, \
NotDeepImmutableError, EmptyPathnameComponentError EmptyPathnameComponentError, MustBeDeepImmutableError, \
MustBeReadonlyError, MustNotBeUnknownRWError
from allmydata.mutable.common import UnrecoverableFileError from allmydata.mutable.common import UnrecoverableFileError
from allmydata.util import abbreviate # TODO: consolidate 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 " "failure, or disk corruption. You should perform a filecheck on "
"this object to learn more.") "this object to learn more.")
return (t, http.GONE) return (t, http.GONE)
if f.check(NotDeepImmutableError): if f.check(MustNotBeUnknownRWError):
t = ("NotDeepImmutableError: a mkdir-immutable operation was given " name = f.value.args[1]
"a child that was not itself immutable: %s" % (f.value,)) 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) return (t, http.BAD_REQUEST)
if f.check(WebError): if f.check(WebError):
return (f.value.text, f.value.code) return (f.value.text, f.value.code)

View File

@ -352,7 +352,12 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
charset = get_arg(req, "_charset", "utf-8") charset = get_arg(req, "_charset", "utf-8")
name = name.decode(charset) name = name.decode(charset)
replace = boolean_of_arg(get_arg(req, "replace", "true")) 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) d.addCallback(lambda res: childcap)
return d return d
@ -363,9 +368,9 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
# won't show up in the resulting encoded form.. the 'name' # won't show up in the resulting encoded form.. the 'name'
# field is completely missing. So to allow deletion of an # field is completely missing. So to allow deletion of an
# empty file, we have to pretend that None means ''. The only # 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 # 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. # buttons ourselves.
name = '' name = ''
charset = get_arg(req, "_charset", "utf-8") charset = get_arg(req, "_charset", "utf-8")
@ -585,7 +590,11 @@ class DirectoryAsHTML(rend.Page):
def render_title(self, ctx, data): def render_title(self, ctx, data):
si_s = abbreviated_dirnode(self.node) si_s = abbreviated_dirnode(self.node)
header = ["Tahoe-LAFS - Directory SI=%s" % si_s] 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)") header.append(" (read-only)")
else: else:
header.append(" (modifiable)") header.append(" (modifiable)")
@ -594,7 +603,11 @@ class DirectoryAsHTML(rend.Page):
def render_header(self, ctx, data): def render_header(self, ctx, data):
si_s = abbreviated_dirnode(self.node) si_s = abbreviated_dirnode(self.node)
header = ["Tahoe-LAFS Directory SI=", T.span(class_="data-chars")[si_s]] 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)") header.append(" (read-only)")
return ctx.tag[header] return ctx.tag[header]
@ -603,7 +616,7 @@ class DirectoryAsHTML(rend.Page):
return T.div[T.a(href=link)["Return to Welcome page"]] return T.div[T.a(href=link)["Return to Welcome page"]]
def render_show_readonly(self, ctx, data): def render_show_readonly(self, ctx, data):
if self.node.is_readonly(): if self.node.is_unknown() or self.node.is_readonly():
return "" return ""
rocap = self.node.get_readonly_uri() rocap = self.node.get_readonly_uri()
root = get_root(ctx) root = get_root(ctx)
@ -630,7 +643,7 @@ class DirectoryAsHTML(rend.Page):
root = get_root(ctx) root = get_root(ctx)
here = "%s/uri/%s/" % (root, urllib.quote(self.node.get_uri())) 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 = "-" delete = "-"
rename = "-" rename = "-"
else: else:
@ -678,8 +691,8 @@ class DirectoryAsHTML(rend.Page):
ctx.fillSlots("times", times) ctx.fillSlots("times", times)
assert IFilesystemNode.providedBy(target), target assert IFilesystemNode.providedBy(target), target
writecap = target.get_uri() or "" target_uri = target.get_uri() or ""
quoted_uri = urllib.quote(writecap, safe="") # escape slashes too quoted_uri = urllib.quote(target_uri, safe="") # escape slashes too
if IMutableFileNode.providedBy(target): if IMutableFileNode.providedBy(target):
# to prevent javascript in displayed .html files from stealing a # to prevent javascript in displayed .html files from stealing a
@ -708,7 +721,7 @@ class DirectoryAsHTML(rend.Page):
elif IDirectoryNode.providedBy(target): elif IDirectoryNode.providedBy(target):
# directory # directory
uri_link = "%s/uri/%s/" % (root, urllib.quote(writecap)) uri_link = "%s/uri/%s/" % (root, urllib.quote(target_uri))
ctx.fillSlots("filename", ctx.fillSlots("filename",
T.a(href=uri_link)[html.escape(name)]) T.a(href=uri_link)[html.escape(name)])
if not target.is_mutable(): if not target.is_mutable():
@ -795,35 +808,30 @@ def DirectoryJSONMetadata(ctx, dirnode):
kids = {} kids = {}
for name, (childnode, metadata) in children.iteritems(): for name, (childnode, metadata) in children.iteritems():
assert IFilesystemNode.providedBy(childnode), childnode assert IFilesystemNode.providedBy(childnode), childnode
rw_uri = childnode.get_uri() rw_uri = childnode.get_write_uri()
ro_uri = childnode.get_readonly_uri() ro_uri = childnode.get_readonly_uri()
if IFileNode.providedBy(childnode): if IFileNode.providedBy(childnode):
if childnode.is_readonly():
rw_uri = None
kiddata = ("filenode", {'size': childnode.get_size(), kiddata = ("filenode", {'size': childnode.get_size(),
'mutable': childnode.is_mutable(), 'mutable': childnode.is_mutable(),
}) })
elif IDirectoryNode.providedBy(childnode): elif IDirectoryNode.providedBy(childnode):
if childnode.is_readonly():
rw_uri = None
kiddata = ("dirnode", {'mutable': childnode.is_mutable()}) kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
else: else:
kiddata = ("unknown", {}) kiddata = ("unknown", {})
kiddata[1]["metadata"] = metadata kiddata[1]["metadata"] = metadata
if ro_uri:
kiddata[1]["ro_uri"] = ro_uri
if rw_uri: if rw_uri:
kiddata[1]["rw_uri"] = rw_uri kiddata[1]["rw_uri"] = rw_uri
if ro_uri:
kiddata[1]["ro_uri"] = ro_uri
verifycap = childnode.get_verify_cap() verifycap = childnode.get_verify_cap()
if verifycap: if verifycap:
kiddata[1]['verify_uri'] = verifycap.to_string() kiddata[1]['verify_uri'] = verifycap.to_string()
kids[name] = kiddata kids[name] = kiddata
if dirnode.is_readonly():
drw_uri = None drw_uri = dirnode.get_write_uri()
dro_uri = dirnode.get_uri() dro_uri = dirnode.get_readonly_uri()
else:
drw_uri = dirnode.get_uri()
dro_uri = dirnode.get_readonly_uri()
contents = { 'children': kids } contents = { 'children': kids }
if dro_uri: if dro_uri:
contents['ro_uri'] = dro_uri contents['ro_uri'] = dro_uri
@ -834,13 +842,13 @@ def DirectoryJSONMetadata(ctx, dirnode):
contents['verify_uri'] = verifycap.to_string() contents['verify_uri'] = verifycap.to_string()
contents['mutable'] = dirnode.is_mutable() contents['mutable'] = dirnode.is_mutable()
data = ("dirnode", contents) data = ("dirnode", contents)
return simplejson.dumps(data, indent=1) + "\n" json = simplejson.dumps(data, indent=1) + "\n"
return json
d.addCallback(_got) d.addCallback(_got)
d.addCallback(text_plain, ctx) d.addCallback(text_plain, ctx)
return d return d
def DirectoryURI(ctx, dirnode): def DirectoryURI(ctx, dirnode):
return text_plain(dirnode.get_uri(), ctx) return text_plain(dirnode.get_uri(), ctx)
@ -1132,18 +1140,39 @@ class DeepCheckStreamer(dirnode.DeepStats):
self.req.write(j+"\n") self.req.write(j+"\n")
return "" return ""
class UnknownNodeHandler(RenderMixin, rend.Page):
class UnknownNodeHandler(RenderMixin, rend.Page):
def __init__(self, client, node, parentnode=None, name=None): def __init__(self, client, node, parentnode=None, name=None):
rend.Page.__init__(self) rend.Page.__init__(self)
assert node assert node
self.node = node self.node = node
self.parentnode = parentnode
self.name = name
def render_GET(self, ctx): def render_GET(self, ctx):
req = IRequest(ctx) req = IRequest(ctx)
t = get_arg(req, "t", "").strip() t = get_arg(req, "t", "").strip()
if t == "info": if t == "info":
return MoreInfo(self.node) 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)

View File

@ -6,10 +6,9 @@ from twisted.internet import defer
from nevow import url, rend from nevow import url, rend
from nevow.inevow import IRequest from nevow.inevow import IRequest
from allmydata.interfaces import ExistingChildError, CannotPackUnknownNodeError from allmydata.interfaces import ExistingChildError
from allmydata.monitor import Monitor from allmydata.monitor import Monitor
from allmydata.immutable.upload import FileHandle from allmydata.immutable.upload import FileHandle
from allmydata.unknown import UnknownNode
from allmydata.util import log, base32 from allmydata.util import log, base32
from allmydata.web.common import text_plain, WebError, RenderMixin, \ 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 from allmydata.web.info import MoreInfo
class ReplaceMeMixin: class ReplaceMeMixin:
def replace_me_with_a_child(self, req, client, replace): def replace_me_with_a_child(self, req, client, replace):
# a new file is being uploaded in our place. # a new file is being uploaded in our place.
mutable = boolean_of_arg(get_arg(req, "mutable", "false")) 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): def replace_me_with_a_childcap(self, req, client, replace):
req.content.seek(0) req.content.seek(0)
childcap = req.content.read() childcap = req.content.read()
childnode = client.create_node_from_uri(childcap, childcap+"readonly") childnode = client.create_node_from_uri(childcap, None, name=self.name)
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)
d = self.parentnode.set_node(self.name, childnode, overwrite=replace) d = self.parentnode.set_node(self.name, childnode, overwrite=replace)
d.addCallback(lambda res: childnode.get_uri()) d.addCallback(lambda res: childnode.get_uri())
return d return d
@ -426,12 +417,8 @@ class FileDownloader(rend.Page):
def FileJSONMetadata(ctx, filenode, edge_metadata): def FileJSONMetadata(ctx, filenode, edge_metadata):
if filenode.is_readonly(): rw_uri = filenode.get_write_uri()
rw_uri = None ro_uri = filenode.get_readonly_uri()
ro_uri = filenode.get_uri()
else:
rw_uri = filenode.get_uri()
ro_uri = filenode.get_readonly_uri()
data = ("filenode", {}) data = ("filenode", {})
data[1]['size'] = filenode.get_size() data[1]['size'] = filenode.get_size()
if ro_uri: if ro_uri:

View File

@ -21,6 +21,8 @@ class MoreInfo(rend.Page):
def get_type(self): def get_type(self):
node = self.original node = self.original
if IDirectoryNode.providedBy(node): if IDirectoryNode.providedBy(node):
if not node.is_mutable():
return "immutable directory"
return "directory" return "directory"
if IFileNode.providedBy(node): if IFileNode.providedBy(node):
si = node.get_storage_index() si = node.get_storage_index()
@ -28,7 +30,7 @@ class MoreInfo(rend.Page):
if node.is_mutable(): if node.is_mutable():
return "mutable file" return "mutable file"
return "immutable file" return "immutable file"
return "LIT file" return "immutable LIT file"
return "unknown" return "unknown"
def render_title(self, ctx, data): def render_title(self, ctx, data):
@ -68,10 +70,10 @@ class MoreInfo(rend.Page):
def render_directory_writecap(self, ctx, data): def render_directory_writecap(self, ctx, data):
node = self.original node = self.original
if node.is_readonly():
return ""
if not IDirectoryNode.providedBy(node): if not IDirectoryNode.providedBy(node):
return "" return ""
if node.is_readonly():
return ""
return ctx.tag[node.get_uri()] return ctx.tag[node.get_uri()]
def render_directory_readcap(self, ctx, data): def render_directory_readcap(self, ctx, data):
@ -86,27 +88,23 @@ class MoreInfo(rend.Page):
return "" return ""
return ctx.tag[node.get_verify_cap().to_string()] return ctx.tag[node.get_verify_cap().to_string()]
def render_file_writecap(self, ctx, data): def render_file_writecap(self, ctx, data):
node = self.original node = self.original
if IDirectoryNode.providedBy(node): if IDirectoryNode.providedBy(node):
node = node._node node = node._node
if ((IDirectoryNode.providedBy(node) or IFileNode.providedBy(node)) write_uri = node.get_write_uri()
and node.is_readonly()): if not write_uri:
return "" return ""
writecap = node.get_uri() return ctx.tag[write_uri]
if not writecap:
return ""
return ctx.tag[writecap]
def render_file_readcap(self, ctx, data): def render_file_readcap(self, ctx, data):
node = self.original node = self.original
if IDirectoryNode.providedBy(node): if IDirectoryNode.providedBy(node):
node = node._node node = node._node
readcap = node.get_readonly_uri() read_uri = node.get_readonly_uri()
if not readcap: if not read_uri:
return "" return ""
return ctx.tag[readcap] return ctx.tag[read_uri]
def render_file_verifycap(self, ctx, data): def render_file_verifycap(self, ctx, data):
node = self.original node = self.original

View File

@ -12,7 +12,7 @@ import allmydata # to display import path
from allmydata import get_package_versions_string from allmydata import get_package_versions_string
from allmydata import provisioning from allmydata import provisioning
from allmydata.util import idlib, log 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 filenode, directory, unlinked, status, operations
from allmydata.web import reliability, storage from allmydata.web import reliability, storage
from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \ from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
@ -85,7 +85,7 @@ class URIHandler(RenderMixin, rend.Page):
try: try:
node = self.client.create_node_from_uri(name) node = self.client.create_node_from_uri(name)
return directory.make_handler_for(node, self.client) 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" raise WebError("'%s' is not a valid file- or directory- cap"
% name) % name)
@ -104,7 +104,7 @@ class FileHandler(rend.Page):
# 'name' must be a file URI # 'name' must be a file URI
try: try:
node = self.client.create_node_from_uri(name) node = self.client.create_node_from_uri(name)
except (TypeError, UnhandledCapTypeError, AssertionError): except (TypeError, AssertionError):
# I think this can no longer be reached # I think this can no longer be reached
raise WebError("'%s' is not a valid file- or directory- cap" raise WebError("'%s' is not a valid file- or directory- cap"
% name) % name)