mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-23 14:52:26 +00:00
webapi: use t=mkdir-with-children instead of a children= arg to t=mkdir .
This is safer: in the earlier API, an old webapi server would silently ignore the initial children, and clients trying to set them would have to fetch the newly-created directory to discover the incompatibility. In the new API, clients using t=mkdir-with-children against an old webapi server will get a clear error.
This commit is contained in:
parent
1273b5c233
commit
768c76aa5f
@ -345,15 +345,20 @@ PUT /uri
|
||||
POST /uri?t=mkdir
|
||||
PUT /uri?t=mkdir
|
||||
|
||||
Create a new directory (either empty or with some initial children) and
|
||||
return its write-cap as the HTTP response body. This does not make the newly
|
||||
created directory visible from the virtual drive. The "PUT" operation is
|
||||
provided for backwards compatibility: new code should use POST.
|
||||
Create a new empty directory and return its write-cap as the HTTP response
|
||||
body. This does not make the newly created directory visible from the
|
||||
virtual drive. The "PUT" operation is provided for backwards compatibility:
|
||||
new code should use POST.
|
||||
|
||||
Initial children are provided in the "children" field of the POST form, or
|
||||
as the request body of the PUT request. This is more efficient than doing
|
||||
separate mkdir and add-children operations. If this value is empty, the new
|
||||
directory will be empty.
|
||||
POST /uri?t=mkdir-with-children
|
||||
|
||||
Create a new directory, populated with a set of child nodes, and return its
|
||||
write-cap as the HTTP response body. The new directory is not attached to
|
||||
any other directory: the returned write-cap is the only reference to it.
|
||||
|
||||
Initial children are provided in the "children" field of the POST form. This
|
||||
is more efficient than doing separate mkdir and add-children operations. If
|
||||
this value is empty, the new directory will be empty.
|
||||
|
||||
If not empty, it will be interpreted as a JSON-encoded dictionary of
|
||||
children with which the new directory should be populated, using the same
|
||||
@ -394,8 +399,7 @@ PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
|
||||
intermediate directories as necessary. If the named target directory already
|
||||
exists, this will make no changes to it.
|
||||
|
||||
If a directory is created, it will be populated with initial children via
|
||||
the PUT request body or POST 'children' form field, as described above.
|
||||
If the final directory is created, it will be empty.
|
||||
|
||||
This will return an error if a blocking file is present at any of the parent
|
||||
names, preventing the server from creating the necessary parent directory.
|
||||
@ -403,17 +407,27 @@ PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
|
||||
The write-cap of the new directory will be returned as the HTTP response
|
||||
body.
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-with-children
|
||||
|
||||
Like above, but if the final directory is created, it will be populated with
|
||||
initial children via the POST 'children' form field, as described above in
|
||||
the /uri?t=mkdir-with-children operation.
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME
|
||||
|
||||
Create a new empty directory and attach it to the given existing directory.
|
||||
This will create additional intermediate directories as necessary. The new
|
||||
directory will be populated with initial children via the PUT request body
|
||||
or POST 'children' form field, as described above.
|
||||
This will create additional intermediate directories as necessary.
|
||||
|
||||
The URL of this form points to the parent of the bottom-most new directory,
|
||||
whereas the previous form has a URL that points directly to the bottom-most
|
||||
new directory.
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME
|
||||
|
||||
As above, but the new directory will be populated with initial children via
|
||||
the POST 'children' form field, as described in /uri?t=mkdir-with-children
|
||||
above.
|
||||
|
||||
=== Get Information About A File Or Directory (as JSON) ===
|
||||
|
||||
GET /uri/$FILECAP?t=json
|
||||
@ -767,7 +781,7 @@ GET /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=info
|
||||
|
||||
POST /uri?t=mkdir
|
||||
|
||||
This creates a new directory, but does not attach it to the virtual
|
||||
This creates a new empty directory, but does not attach it to the virtual
|
||||
filesystem.
|
||||
|
||||
If a "redirect_to_result=true" argument is provided, then the HTTP response
|
||||
@ -783,13 +797,10 @@ POST /uri?t=mkdir
|
||||
"false"), then the HTTP response body will simply be the write-cap of the
|
||||
new directory.
|
||||
|
||||
It accepts the same initial-children arguments as described in earlier
|
||||
t=mkdir sections, but these are unlikely to be useful from a browser form.
|
||||
|
||||
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=CHILDNAME
|
||||
|
||||
This creates a new directory as a child of the designated SUBDIR. This will
|
||||
create additional intermediate directories as necessary.
|
||||
This creates a new empty directory as a child of the designated SUBDIR. This
|
||||
will create additional intermediate directories as necessary.
|
||||
|
||||
If a "when_done=URL" argument is provided, the HTTP response will cause the
|
||||
web browser to redirect to the given URL. This provides a convenient way to
|
||||
@ -797,9 +808,6 @@ POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=CHILDNAME
|
||||
when_done= argument, the HTTP response will simply contain the write-cap of
|
||||
the directory that was just created.
|
||||
|
||||
It accepts the same initial-children arguments as described in earlier
|
||||
t=mkdir sections, but these are unlikely to be useful from a browser form.
|
||||
|
||||
|
||||
=== Uploading a File ===
|
||||
|
||||
|
@ -1122,42 +1122,10 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d.addCallback(self.failUnlessNodeKeysAre, [])
|
||||
return d
|
||||
|
||||
def test_PUT_NEWDIRURL_initial_children(self):
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
d = self.PUT(self.public_url + "/foo/newdir?t=mkdir",
|
||||
simplejson.dumps(newkids))
|
||||
def _check(uri):
|
||||
n = self.s.create_node_from_uri(uri.strip())
|
||||
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
|
||||
d2.addCallback(lambda ign:
|
||||
n.get_child_and_metadata_at_path(u"child-imm"))
|
||||
d2.addCallback(lambda (c1, md1):
|
||||
self.failUnlessEqual(md1["metakey1"], "metavalue1"))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-mutable",
|
||||
filecap2))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-mutable-ro",
|
||||
filecap3))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"dirchild", dircap))
|
||||
return d2
|
||||
d.addCallback(_check)
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
|
||||
return d
|
||||
|
||||
def test_POST_NEWDIRURL_initial_children(self):
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
d = self.POST(self.public_url + "/foo/newdir?t=mkdir",
|
||||
d = self.POST(self.public_url + "/foo/newdir?t=mkdir-with-children",
|
||||
children=simplejson.dumps(newkids))
|
||||
def _check(uri):
|
||||
n = self.s.create_node_from_uri(uri.strip())
|
||||
@ -1930,8 +1898,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
|
||||
def test_POST_mkdir_initial_children(self):
|
||||
newkids, filecap1, ign, ign, ign = self._create_initial_children()
|
||||
d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir",
|
||||
children=simplejson.dumps(newkids))
|
||||
d = self.POST(self.public_url + "/foo", t="mkdir-with-children",
|
||||
name="newdir", children=simplejson.dumps(newkids))
|
||||
d.addCallback(lambda res:
|
||||
self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
|
||||
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
|
||||
@ -1992,7 +1960,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
def test_POST_mkdir_no_parentdir_initial_children(self):
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
d = self.POST("/uri?t=mkdir", children=simplejson.dumps(newkids))
|
||||
d = self.POST("/uri?t=mkdir-with-children",
|
||||
children=simplejson.dumps(newkids))
|
||||
def _after_mkdir(res):
|
||||
self.failUnless(res.startswith("URI:DIR"), res)
|
||||
n = self.s.create_node_from_uri(res)
|
||||
@ -2011,6 +1980,19 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d.addCallback(_after_mkdir)
|
||||
return d
|
||||
|
||||
def test_POST_mkdir_no_parentdir_unexpected_children(self):
|
||||
# the regular /uri?t=mkdir operation is specified to ignore its body.
|
||||
# Only t=mkdir-with-children pays attention to it.
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
d = self.shouldHTTPError("POST t=mkdir unexpected children",
|
||||
400, "Bad Request",
|
||||
"t=mkdir does not accept children=, "
|
||||
"try t=mkdir-with-children instead",
|
||||
self.POST, "/uri?t=mkdir", # without children
|
||||
children=simplejson.dumps(newkids))
|
||||
return d
|
||||
|
||||
def test_POST_noparent_bad(self):
|
||||
d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
|
||||
"/uri accepts only PUT, PUT?t=mkdir, "
|
||||
@ -2503,27 +2485,6 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
|
||||
d.addCallback(self.failUnlessIsEmptyJSON)
|
||||
return d
|
||||
|
||||
def test_PUT_mkdir_initial_children(self):
|
||||
(newkids, filecap1, filecap2, filecap3,
|
||||
dircap) = self._create_initial_children()
|
||||
d = self.PUT("/uri?t=mkdir", simplejson.dumps(newkids))
|
||||
def _check(uri):
|
||||
n = self.s.create_node_from_uri(uri.strip())
|
||||
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-mutable",
|
||||
filecap2))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"child-mutable-ro",
|
||||
filecap3))
|
||||
d2.addCallback(lambda ign:
|
||||
self.failUnlessChildURIIs(n, u"dirchild", dircap))
|
||||
return d2
|
||||
d.addCallback(_check)
|
||||
return d
|
||||
|
||||
def test_POST_check(self):
|
||||
d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
|
||||
def _done(res):
|
||||
|
@ -54,13 +54,13 @@ def get_arg(ctx_or_req, argname, default=None, multiple=False):
|
||||
return results[0]
|
||||
return default
|
||||
|
||||
def convert_initial_children_json(nodemaker, initial_children_json):
|
||||
def convert_children_json(nodemaker, children_json):
|
||||
"""I convert the JSON output of GET?t=json into the dict-of-nodes input
|
||||
to both dirnode.create_subdirectory() and
|
||||
client.create_directory(initial_children=)."""
|
||||
initial_children = {}
|
||||
if initial_children_json:
|
||||
data = simplejson.loads(initial_children_json)
|
||||
if children_json:
|
||||
data = simplejson.loads(children_json)
|
||||
for (name, (ctype, propdict)) in data.iteritems():
|
||||
name = unicode(name)
|
||||
writecap = propdict.get("rw_uri")
|
||||
|
@ -22,7 +22,7 @@ from allmydata.web.common import text_plain, WebError, \
|
||||
IOpHandleTable, NeedOperationHandleError, \
|
||||
boolean_of_arg, get_arg, get_root, parse_replace_arg, \
|
||||
should_create_intermediate_directories, \
|
||||
getxmlfile, RenderMixin, humanize_failure, convert_initial_children_json
|
||||
getxmlfile, RenderMixin, humanize_failure, convert_children_json
|
||||
from allmydata.web.filenode import ReplaceMeMixin, \
|
||||
FileNodeHandler, PlaceHolderNodeHandler
|
||||
from allmydata.web.check_results import CheckResults, \
|
||||
@ -93,16 +93,15 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
else:
|
||||
if DEBUG: print " terminal"
|
||||
# terminal node
|
||||
if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir") ]:
|
||||
if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir"),
|
||||
("POST", "mkdir-with-children") ]:
|
||||
if DEBUG: print " making final directory"
|
||||
# final directory
|
||||
if method == "POST":
|
||||
kids = {}
|
||||
if (method,t) == ("POST", "mkdir-with-children"):
|
||||
kids_json = get_arg(req, "children", "")
|
||||
else:
|
||||
req.content.seek(0)
|
||||
kids_json = req.content.read()
|
||||
kids = convert_initial_children_json(self.client.nodemaker,
|
||||
kids_json)
|
||||
kids = convert_children_json(self.client.nodemaker,
|
||||
kids_json)
|
||||
d = self.node.create_subdirectory(name, kids)
|
||||
d.addCallback(make_handler_for,
|
||||
self.client, self.node, name)
|
||||
@ -185,6 +184,8 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
|
||||
if t == "mkdir":
|
||||
d = self._POST_mkdir(req)
|
||||
elif t == "mkdir-with-children":
|
||||
d = self._POST_mkdir_with_children(req)
|
||||
elif t == "mkdir-p":
|
||||
# TODO: docs, tests
|
||||
d = self._POST_mkdir_p(req)
|
||||
@ -221,6 +222,19 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
return d
|
||||
|
||||
def _POST_mkdir(self, req):
|
||||
name = get_arg(req, "name", "")
|
||||
if not name:
|
||||
# our job is done, it was handled by the code in got_child
|
||||
# which created the final directory (i.e. us)
|
||||
return defer.succeed(self.node.get_uri()) # TODO: urlencode
|
||||
name = name.decode("utf-8")
|
||||
replace = boolean_of_arg(get_arg(req, "replace", "true"))
|
||||
kids = {}
|
||||
d = self.node.create_subdirectory(name, kids, overwrite=replace)
|
||||
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
|
||||
return d
|
||||
|
||||
def _POST_mkdir_with_children(self, req):
|
||||
name = get_arg(req, "name", "")
|
||||
if not name:
|
||||
# our job is done, it was handled by the code in got_child
|
||||
@ -229,7 +243,7 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
name = name.decode("utf-8")
|
||||
replace = boolean_of_arg(get_arg(req, "replace", "true"))
|
||||
kids_json = get_arg(req, "children", "")
|
||||
kids = convert_initial_children_json(self.client.nodemaker, kids_json)
|
||||
kids = convert_children_json(self.client.nodemaker, kids_json)
|
||||
d = self.node.create_subdirectory(name, kids, overwrite=replace)
|
||||
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
|
||||
return d
|
||||
|
@ -70,6 +70,9 @@ class URIHandler(RenderMixin, rend.Page):
|
||||
return unlinked.POSTUnlinkedCHK(req, self.client)
|
||||
if t == "mkdir":
|
||||
return unlinked.POSTUnlinkedCreateDirectory(req, self.client)
|
||||
elif t == "mkdir-with-children":
|
||||
return unlinked.POSTUnlinkedCreateDirectoryWithChildren(req,
|
||||
self.client)
|
||||
errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
|
||||
"and POST?t=mkdir")
|
||||
raise WebError(errmsg, http.BAD_REQUEST)
|
||||
|
@ -5,7 +5,7 @@ from twisted.internet import defer
|
||||
from nevow import rend, url, tags as T
|
||||
from allmydata.immutable.upload import FileHandle
|
||||
from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
|
||||
convert_initial_children_json
|
||||
convert_children_json, WebError
|
||||
from allmydata.web import status
|
||||
|
||||
def PUTUnlinkedCHK(req, client):
|
||||
@ -26,10 +26,7 @@ def PUTUnlinkedSSK(req, client):
|
||||
|
||||
def PUTUnlinkedCreateDirectory(req, client):
|
||||
# "PUT /uri?t=mkdir", to create an unlinked directory.
|
||||
req.content.seek(0)
|
||||
kids_json = req.content.read()
|
||||
kids = convert_initial_children_json(client.nodemaker, kids_json)
|
||||
d = client.create_dirnode(initial_children=kids)
|
||||
d = client.create_dirnode()
|
||||
d.addCallback(lambda dirnode: dirnode.get_uri())
|
||||
# XXX add redirect_to_result
|
||||
return d
|
||||
@ -93,9 +90,30 @@ def POSTUnlinkedSSK(req, client):
|
||||
return d
|
||||
|
||||
def POSTUnlinkedCreateDirectory(req, client):
|
||||
# "POST /uri?t=mkdir", to create an unlinked directory.
|
||||
kids_json = get_arg(req, "children", None)
|
||||
if kids_json is not None:
|
||||
raise WebError("t=mkdir does not accept children=, "
|
||||
"try t=mkdir-with-children instead",
|
||||
http.BAD_REQUEST)
|
||||
d = client.create_dirnode()
|
||||
redirect = get_arg(req, "redirect_to_result", "false")
|
||||
if boolean_of_arg(redirect):
|
||||
def _then_redir(res):
|
||||
new_url = "uri/" + urllib.quote(res.get_uri())
|
||||
req.setResponseCode(http.SEE_OTHER) # 303
|
||||
req.setHeader('location', new_url)
|
||||
req.finish()
|
||||
return ''
|
||||
d.addCallback(_then_redir)
|
||||
else:
|
||||
d.addCallback(lambda dirnode: dirnode.get_uri())
|
||||
return d
|
||||
|
||||
def POSTUnlinkedCreateDirectoryWithChildren(req, client):
|
||||
# "POST /uri?t=mkdir", to create an unlinked directory.
|
||||
kids_json = get_arg(req, "children", "")
|
||||
kids = convert_initial_children_json(client.nodemaker, kids_json)
|
||||
kids = convert_children_json(client.nodemaker, kids_json)
|
||||
d = client.create_dirnode(initial_children=kids)
|
||||
redirect = get_arg(req, "redirect_to_result", "false")
|
||||
if boolean_of_arg(redirect):
|
||||
|
Loading…
Reference in New Issue
Block a user