from allmydata.util import base32, time_format
from allmydata.uri import from_string_dirnode
from allmydata.interfaces import IDirectoryNode, IFileNode, IFilesystemNode, \
- IImmutableFileNode, IMutableFileNode, ExistingChildError, NoSuchChildError
+ IImmutableFileNode, IMutableFileNode, ExistingChildError, \
+ NoSuchChildError, EmptyPathnameComponentError, SDMF_VERSION, MDMF_VERSION
+from allmydata.blacklist import ProhibitedNode
from allmydata.monitor import Monitor, OperationCancelledError
from allmydata import dirnode
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_children_json
+ getxmlfile, RenderMixin, humanize_failure, convert_children_json, \
+ parse_mutable_type_arg
from allmydata.web.filenode import ReplaceMeMixin, \
FileNodeHandler, PlaceHolderNodeHandler
from allmydata.web.check_results import CheckResults, \
self.name = name
def childFactory(self, ctx, name):
- req = IRequest(ctx)
name = name.decode("utf-8")
+ if not name:
+ raise EmptyPathnameComponentError()
d = self.node.get(name)
d.addBoth(self.got_child, ctx, name)
# got_child returns a handler resource: FileNodeHandler or
mutable = True
if t == "mkdir-immutable":
mutable = False
+
+ mt = None
+ if mutable:
+ arg = get_arg(req, "mutable-type", None)
+ mt = parse_mutable_type_arg(arg)
+ if mt is "invalid":
+ raise WebError("Unknown type: %s" % arg,
+ http.BAD_REQUEST)
d = self.node.create_subdirectory(name, kids,
- mutable=mutable)
+ mutable=mutable,
+ mutable_version=mt)
d.addCallback(make_handler_for,
self.client, self.node, name)
return d
if not t:
# render the directory as HTML, using the docFactory and Nevow's
# whole templating thing.
- return DirectoryAsHTML(self.node)
+ return DirectoryAsHTML(self.node,
+ self.client.mutable_file_default)
if t == "json":
return DirectoryJSONMetadata(ctx, self.node)
d = self._POST_upload(ctx) # this one needs the context
elif t == "uri":
d = self._POST_uri(req)
- elif t == "delete":
- d = self._POST_delete(req)
+ elif t == "delete" or t == "unlink":
+ d = self._POST_unlink(req)
elif t == "rename":
d = self._POST_rename(req)
elif t == "check":
d = self._POST_start_deep_stats(ctx)
elif t == "stream-manifest":
d = self._POST_stream_manifest(ctx)
- elif t == "set_children":
+ elif t == "set_children" or t == "set-children":
d = self._POST_set_children(req)
else:
raise WebError("POST to a directory with bad t=%s" % t)
name = name.decode("utf-8")
replace = boolean_of_arg(get_arg(req, "replace", "true"))
kids = {}
- d = self.node.create_subdirectory(name, kids, overwrite=replace)
+ arg = get_arg(req, "mutable-type", None)
+ mt = parse_mutable_type_arg(arg)
+ if mt is not None and mt is not "invalid":
+ d = self.node.create_subdirectory(name, kids, overwrite=replace,
+ mutable_version=mt)
+ elif mt is "invalid":
+ raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
+ else:
+ d = self.node.create_subdirectory(name, kids, overwrite=replace)
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d
# 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"))
+ # TODO: decide on replace= behavior, see #903
+ #replace = boolean_of_arg(get_arg(req, "replace", "false"))
req.content.seek(0)
kids_json = req.content.read()
kids = convert_children_json(self.client.nodemaker, kids_json)
- d = self.node.create_subdirectory(name, kids, overwrite=replace)
+ arg = get_arg(req, "mutable-type", None)
+ mt = parse_mutable_type_arg(arg)
+ if mt is not None and mt is not "invalid":
+ d = self.node.create_subdirectory(name, kids, overwrite=False,
+ mutable_version=mt)
+ elif mt is "invalid":
+ raise WebError("Unknown type: %s" % arg)
+ else:
+ d = self.node.create_subdirectory(name, kids, overwrite=False)
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d
# 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"))
+ # TODO: decide on replace= behavior, see #903
+ #replace = boolean_of_arg(get_arg(req, "replace", "false"))
req.content.seek(0)
kids_json = req.content.read()
kids = convert_children_json(self.client.nodemaker, kids_json)
- d = self.node.create_subdirectory(name, kids, mutable=False)
+ d = self.node.create_subdirectory(name, kids, overwrite=False, mutable=False)
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d
charset = get_arg(req, "_charset", "utf-8")
name = name.decode(charset)
replace = boolean_of_arg(get_arg(req, "replace", "true"))
- d = self.node.set_uri(name, childcap, childcap, overwrite=replace)
+
+ # We mustn't pass childcap for the readcap argument because we don't
+ # know whether it is a read cap. Passing a read cap as the writecap
+ # argument will work (it ends up calling NodeMaker.create_from_cap,
+ # which derives a readcap if necessary and possible).
+ d = self.node.set_uri(name, childcap, None, overwrite=replace)
d.addCallback(lambda res: childcap)
return d
- def _POST_delete(self, req):
+ def _POST_unlink(self, req):
name = get_arg(req, "name")
if name is None:
# apparently an <input type="hidden" name="name" value="">
# won't show up in the resulting encoded form.. the 'name'
- # field is completely missing. So to allow deletion of an
- # empty file, we have to pretend that None means ''. The only
- # downide of this is a slightly confusing error message if
- # someone does a POST without a name= field. For our own HTML
- # thisn't a big deal, because we create the 'delete' POST
- # buttons ourselves.
+ # field is completely missing. So to allow unlinking of a
+ # child with a name that is the empty string, we have to
+ # pretend that None means ''. The only downside of this is
+ # a slightly confusing error message if someone does a POST
+ # without a name= field. For our own HTML this isn't a big
+ # deal, because we create the 'unlink' POST buttons ourselves.
name = ''
charset = get_arg(req, "_charset", "utf-8")
name = name.decode(charset)
d = self.node.delete(name)
- d.addCallback(lambda res: "thing deleted")
+ d.addCallback(lambda res: "thing unlinked")
return d
def _POST_rename(self, req):
docFactory = getxmlfile("directory.xhtml")
addSlash = True
- def __init__(self, node):
+ def __init__(self, node, default_mutable_format):
rend.Page.__init__(self)
self.node = node
+ assert default_mutable_format in (MDMF_VERSION, SDMF_VERSION)
+ self.default_mutable_format = default_mutable_format
+
def beforeRender(self, ctx):
# attempt to get the dirnode's children, stashing them (or the
# failure that results) for later use
def render_title(self, ctx, data):
si_s = abbreviated_dirnode(self.node)
header = ["Tahoe-LAFS - Directory SI=%s" % si_s]
- if self.node.is_readonly():
+ if self.node.is_unknown():
+ header.append(" (unknown)")
+ elif not self.node.is_mutable():
+ header.append(" (immutable)")
+ elif self.node.is_readonly():
header.append(" (read-only)")
else:
header.append(" (modifiable)")
def render_header(self, ctx, data):
si_s = abbreviated_dirnode(self.node)
header = ["Tahoe-LAFS Directory SI=", T.span(class_="data-chars")[si_s]]
- if self.node.is_readonly():
+ if self.node.is_unknown():
+ header.append(" (unknown)")
+ elif not self.node.is_mutable():
+ header.append(" (immutable)")
+ elif self.node.is_readonly():
header.append(" (read-only)")
return ctx.tag[header]
def render_welcome(self, ctx, data):
link = get_root(ctx)
- return T.div[T.a(href=link)["Return to Welcome page"]]
+ return ctx.tag[T.a(href=link)["Return to Welcome page"]]
def render_show_readonly(self, ctx, data):
- if self.node.is_readonly():
+ if self.node.is_unknown() or self.node.is_readonly():
return ""
rocap = self.node.get_readonly_uri()
root = get_root(ctx)
root = get_root(ctx)
here = "%s/uri/%s/" % (root, urllib.quote(self.node.get_uri()))
- if self.node.is_readonly():
- delete = "-"
+ if self.node.is_unknown() or self.node.is_readonly():
+ unlink = "-"
rename = "-"
else:
- # this creates a button which will cause our child__delete method
- # to be invoked, which deletes the file and then redirects the
+ # this creates a button which will cause our _POST_unlink method
+ # to be invoked, which unlinks the file and then redirects the
# browser back to this directory
- delete = T.form(action=here, method="post")[
- T.input(type='hidden', name='t', value='delete'),
+ unlink = T.form(action=here, method="post")[
+ T.input(type='hidden', name='t', value='unlink'),
T.input(type='hidden', name='name', value=name),
T.input(type='hidden', name='when_done', value="."),
- T.input(type='submit', value='del', name="del"),
+ T.input(type='submit', value='unlink', name="unlink"),
]
rename = T.form(action=here, method="get")[
T.input(type='submit', value='rename', name="rename"),
]
- ctx.fillSlots("delete", delete)
+ ctx.fillSlots("unlink", unlink)
ctx.fillSlots("rename", rename)
times = []
ctx.fillSlots("times", times)
assert IFilesystemNode.providedBy(target), target
- writecap = target.get_uri() or ""
- quoted_uri = urllib.quote(writecap, safe="") # escape slashes too
+ target_uri = target.get_uri() or ""
+ quoted_uri = urllib.quote(target_uri, safe="") # escape slashes too
if IMutableFileNode.providedBy(target):
# to prevent javascript in displayed .html files from stealing a
elif IDirectoryNode.providedBy(target):
# directory
- uri_link = "%s/uri/%s/" % (root, urllib.quote(writecap))
+ uri_link = "%s/uri/%s/" % (root, urllib.quote(target_uri))
ctx.fillSlots("filename",
T.a(href=uri_link)[html.escape(name)])
if not target.is_mutable():
ctx.fillSlots("size", "-")
info_link = "%s/uri/%s/?t=info" % (root, quoted_uri)
+ elif isinstance(target, ProhibitedNode):
+ ctx.fillSlots("filename", T.strike[name])
+ if IDirectoryNode.providedBy(target.wrapped_node):
+ blacklisted_type = "DIR-BLACKLISTED"
+ else:
+ blacklisted_type = "BLACKLISTED"
+ ctx.fillSlots("type", blacklisted_type)
+ ctx.fillSlots("size", "-")
+ info_link = None
+ ctx.fillSlots("info", ["Access Prohibited:", T.br, target.reason])
+
else:
# unknown
ctx.fillSlots("filename", html.escape(name))
- ctx.fillSlots("type", "?")
+ if target.get_write_uri() is not None:
+ unknowntype = "?"
+ elif not self.node.is_mutable() or target.is_alleged_immutable():
+ unknowntype = "?-IMM"
+ else:
+ unknowntype = "?-RO"
+ ctx.fillSlots("type", unknowntype)
ctx.fillSlots("size", "-")
# use a directory-relative info link, so we can extract both the
# writecap and the readcap
info_link = "%s?t=info" % urllib.quote(name)
- ctx.fillSlots("info", T.a(href=info_link)["More Info"])
+ if info_link:
+ ctx.fillSlots("info", T.a(href=info_link)["More Info"])
return ctx.tag
+ # XXX: Duplicated from root.py.
def render_forms(self, ctx, data):
forms = []
if self.dirnode_children is None:
return T.div["No upload forms: directory is unreadable"]
+ mdmf_directory_input = T.input(type='radio', name='mutable-type',
+ id='mutable-directory-mdmf',
+ value='mdmf')
+ sdmf_directory_input = T.input(type='radio', name='mutable-type',
+ id='mutable-directory-sdmf',
+ value='sdmf', checked='checked')
mkdir = T.form(action=".", method="post",
enctype="multipart/form-data")[
T.fieldset[
T.legend(class_="freeform-form-label")["Create a new directory in this directory"],
"New directory name: ",
T.input(type="text", name="name"), " ",
+ T.label(for_='mutable-directory-sdmf')["SDMF"],
+ sdmf_directory_input,
+ T.label(for_='mutable-directory-mdmf')["MDMF"],
+ mdmf_directory_input,
T.input(type="submit", value="Create"),
]]
forms.append(T.div(class_="freeform-form")[mkdir])
+ # Build input elements for mutable file type. We do this outside
+ # of the list so we can check the appropriate format, based on
+ # the default configured in the client (which reflects the
+ # default configured in tahoe.cfg)
+ if self.default_mutable_format == MDMF_VERSION:
+ mdmf_input = T.input(type='radio', name='mutable-type',
+ id='mutable-type-mdmf', value='mdmf',
+ checked='checked')
+ else:
+ mdmf_input = T.input(type='radio', name='mutable-type',
+ id='mutable-type-mdmf', value='mdmf')
+
+ if self.default_mutable_format == SDMF_VERSION:
+ sdmf_input = T.input(type='radio', name='mutable-type',
+ id='mutable-type-sdmf', value='sdmf',
+ checked="checked")
+ else:
+ sdmf_input = T.input(type='radio', name='mutable-type',
+ id='mutable-type-sdmf', value='sdmf')
+
upload = T.form(action=".", method="post",
enctype="multipart/form-data")[
T.fieldset[
T.input(type="submit", value="Upload"),
" Mutable?:",
T.input(type="checkbox", name="mutable"),
+ sdmf_input, T.label(for_="mutable-type-sdmf")["SDMF"],
+ mdmf_input,
+ T.label(for_="mutable-type-mdmf")["MDMF (experimental)"],
]]
forms.append(T.div(class_="freeform-form")[upload])
kids = {}
for name, (childnode, metadata) in children.iteritems():
assert IFilesystemNode.providedBy(childnode), childnode
- rw_uri = childnode.get_uri()
+ rw_uri = childnode.get_write_uri()
ro_uri = childnode.get_readonly_uri()
if IFileNode.providedBy(childnode):
- if childnode.is_readonly():
- rw_uri = None
kiddata = ("filenode", {'size': childnode.get_size(),
'mutable': childnode.is_mutable(),
})
+ if childnode.is_mutable() and \
+ childnode.get_version() is not None:
+ mutable_type = childnode.get_version()
+ assert mutable_type in (SDMF_VERSION, MDMF_VERSION)
+
+ if mutable_type == MDMF_VERSION:
+ mutable_type = "mdmf"
+ else:
+ mutable_type = "sdmf"
+ kiddata[1]['mutable-type'] = mutable_type
+
elif IDirectoryNode.providedBy(childnode):
- if childnode.is_readonly():
- rw_uri = None
kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
else:
kiddata = ("unknown", {})
+
kiddata[1]["metadata"] = metadata
- if ro_uri:
- kiddata[1]["ro_uri"] = ro_uri
if rw_uri:
kiddata[1]["rw_uri"] = rw_uri
+ if ro_uri:
+ kiddata[1]["ro_uri"] = ro_uri
verifycap = childnode.get_verify_cap()
if verifycap:
kiddata[1]['verify_uri'] = verifycap.to_string()
+
kids[name] = kiddata
- if dirnode.is_readonly():
- drw_uri = None
- dro_uri = dirnode.get_uri()
- else:
- drw_uri = dirnode.get_uri()
- dro_uri = dirnode.get_readonly_uri()
+
+ drw_uri = dirnode.get_write_uri()
+ dro_uri = dirnode.get_readonly_uri()
contents = { 'children': kids }
if dro_uri:
contents['ro_uri'] = dro_uri
contents['verify_uri'] = verifycap.to_string()
contents['mutable'] = dirnode.is_mutable()
data = ("dirnode", contents)
- return simplejson.dumps(data, indent=1) + "\n"
+ json = simplejson.dumps(data, indent=1) + "\n"
+ return json
d.addCallback(_got)
d.addCallback(text_plain, ctx)
return d
-
def DirectoryURI(ctx, dirnode):
return text_plain(dirnode.get_uri(), ctx)
m = self.monitor
s = m.get_status()
+ if m.origin_si:
+ origin_base32 = base32.b2a(m.origin_si)
+ else:
+ origin_base32 = ""
status = { "stats": s["stats"],
"finished": m.is_finished(),
- "origin": base32.b2a(m.origin_si),
+ "origin": origin_base32,
}
if m.is_finished():
# don't return manifest/verifycaps/SIs unless the operation is
return simplejson.dumps(status, indent=1)
def _si_abbrev(self):
- return base32.b2a(self.monitor.origin_si)[:6]
+ si = self.monitor.origin_si
+ if not si:
+ return "<LIT>"
+ return base32.b2a(si)[:6]
def render_title(self, ctx):
return T.title["Manifest of SI=%s" % self._si_abbrev()]
v = node.get_verify_cap()
if v:
v = v.to_string()
- d["verifycap"] = v
+ d["verifycap"] = v or ""
r = node.get_repair_cap()
if r:
r = r.to_string()
- d["repaircap"] = r
+ d["repaircap"] = r or ""
si = node.get_storage_index()
if si:
si = base32.b2a(si)
- d["storage-index"] = si
+ d["storage-index"] = si or ""
j = simplejson.dumps(d, ensure_ascii=True)
assert "\n" not in j
if IDirectoryNode.providedBy(node):
data["type"] = "directory"
- else:
+ elif IFileNode.providedBy(node):
data["type"] = "file"
+ else:
+ data["type"] = "unknown"
v = node.get_verify_cap()
if v:
v = v.to_string()
- data["verifycap"] = v
+ data["verifycap"] = v or ""
r = node.get_repair_cap()
if r:
r = r.to_string()
- data["repaircap"] = r
+ data["repaircap"] = r or ""
si = node.get_storage_index()
if si:
si = base32.b2a(si)
- data["storage-index"] = si
+ data["storage-index"] = si or ""
if self.repair:
d = node.check_and_repair(self.monitor, self.verify, self.add_lease)
self.req.write(j+"\n")
return ""
-class UnknownNodeHandler(RenderMixin, rend.Page):
+class UnknownNodeHandler(RenderMixin, rend.Page):
def __init__(self, client, node, parentnode=None, name=None):
rend.Page.__init__(self)
assert node
self.node = node
+ self.parentnode = parentnode
+ self.name = name
def render_GET(self, ctx):
req = IRequest(ctx)
t = get_arg(req, "t", "").strip()
if t == "info":
return MoreInfo(self.node)
- raise WebError("GET unknown URI type: can only do t=info, not t=%s" % t)
-
-
+ if t == "json":
+ is_parent_known_immutable = self.parentnode and not self.parentnode.is_mutable()
+ 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, is_parent_known_immutable))
+ 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, node, edge_metadata, is_parent_known_immutable):
+ rw_uri = node.get_write_uri()
+ ro_uri = node.get_readonly_uri()
+ data = ("unknown", {})
+ if ro_uri:
+ data[1]['ro_uri'] = ro_uri
+ if rw_uri:
+ data[1]['rw_uri'] = rw_uri
+ data[1]['mutable'] = True
+ elif is_parent_known_immutable or node.is_alleged_immutable():
+ data[1]['mutable'] = False
+ # else we don't know whether it is mutable.
+
+ if edge_metadata is not None:
+ data[1]['metadata'] = edge_metadata
+ return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx)