== POST Forms ==
- POST DIRURL?t=upload-form
+ POST DIRURL
+ t=upload
+ name=childname (optional)
+ file=newfile
This instructs the client to upload a file into the given dirnode. We need
this because forms are the only way for a web browser to upload a file
new child name will be included in the form's arguments. This can only be
used to upload a single file at a time.
- POST DIRURL?t=mkdir-form
+ POST DIRURL
+ t=mkdir
+ name=childname
This instructs the client to create a new empty directory. The name of the
new child directory will be included in the form's arguments.
- POST DIRURL?t=put-uri-form
+ POST DIRURL
+ t=uri
+ name=childname
+ uri=newuri
This instructs the client to attach a child that is referenced by URI (just
like the PUT NEWFILEURL?t=uri method). The name and URI of the new child
will be included in the form's arguments.
- POST DIRURL?t=delete-form
+ POST DIRURL
+ t=delete
+ name=childname
This instructs the client to delete a file from the given dirnode. The name
of the child to be deleted will be included in the form's arguments.
GET http://localhost:8011/uri/$URI
- would retrieve the contents of the file. If the URI corresponds to a
- directory, then:
+ would retrieve the contents of the file. Since files accessed this way do
+ not have a naturally-occurring filename (from which a MIME-type can be
+ derived), one can be specified using a 'filename=' query argument. This
+ filename is also the one used if the 'save=true' argument is set, which
+ adds a 'Content-Disposition: attachment' header to prompt most web browsers
+ to save the file to disk rather than attempting to display it:
+
+ GET http://localhost:8011/uri/$URI?filename=foo.jpg
+ GET http://localhost:8011/uri/$URI?filename=foo.jpg&save=true
+
+ If the URI corresponds to a directory, then:
PUT http://localhost:8011/uri/$URI/subdir/newfile?localfile=$FILENAME
can be used to attach a shared directory to the vdrive. Intermediate
directories are created on-demand just like with the regular PUT command.
+ GET http://localhost:8011/uri?uri=$URI
+
+ This causes a redirect to /uri/$URI, and retains any additional query
+ arguments (like filename= or save=). This is for the convenience of web
+ forms which allow the user to paste in a URI (obtained through some
+ out-of-band channel, like IM or email).
+
+ Note that this form only redirects to the specific node indicated by the
+ URI: unlike the GET /uri/$URI form, you cannot traverse to child nodes by
+ appending additional path segments to the URL.
+
== XMLRPC ==
http://localhost:8011/xmlrpc
-import re, os.path
+import re, os.path, urllib
from zope.interface import implements
from twisted.application import service
from twisted.trial import unittest
from twisted.internet import defer
from twisted.web import client, error
-from twisted.python import failure
+from twisted.python import failure, log
from allmydata import webish, interfaces, dirnode, uri
from allmydata.encode import NotEnoughPeersError
from allmydata.util import fileutil
name = "vdrive"
public_root = None
private_root = None
+ def __init__(self, nodes):
+ self._my_nodes = nodes
def have_public_root(self):
return bool(self.public_root)
def have_private_root(self):
def get_private_root(self):
return defer.succeed(self.private_root)
+ def get_node(self, uri):
+ def _try():
+ return self._my_nodes[uri]
+ return defer.maybeDeferred(_try)
+
class Web(unittest.TestCase):
def setUp(self):
self.s = MyClient()
port = s.listener._port.getHost().port
self.webish_url = "http://localhost:%d" % port
- v = MyVirtualDrive()
- v.setServiceParent(self.s)
-
self.nodes = {} # maps URI to node
self.files = {} # maps file URI to contents
+
+ v = MyVirtualDrive(self.nodes)
+ v.setServiceParent(self.s)
+
dl = MyDownloader(self.files)
dl.setServiceParent(self.s)
ul = MyUploader(self.files)
def failUnlessIsBarDotTxt(self, res):
self.failUnlessEqual(res, self.BAR_CONTENTS)
- def GET(self, urlpath):
+ def failUnlessIsFooJSON(self, res):
+ self.failUnless("JSONny stuff here" in res)
+ self.failUnless("name=bar.txt, child_uri=%s" % self._bar_txt_uri
+ in res)
+ self.failUnless("name=blockingfile" in res)
+
+ def GET(self, urlpath, followRedirect=False):
url = self.webish_url + urlpath
- return client.getPage(url, method="GET")
+ return client.getPage(url, method="GET", followRedirect=followRedirect)
def PUT(self, urlpath, data):
url = self.webish_url + urlpath
url = self.webish_url + urlpath
return client.getPage(url, method="DELETE")
- def POST(self, urlpath, data):
- raise unittest.SkipTest("not yet")
+ def POST(self, urlpath, **fields):
url = self.webish_url + urlpath
- return client.getPage(url, method="POST", postdata=data)
+ sepbase = "boogabooga"
+ sep = "--" + sepbase
+ form = []
+ form.append(sep)
+ form.append('Content-Disposition: form-data; name="_charset"')
+ form.append('')
+ form.append('UTF-8')
+ form.append(sep)
+ for name, value in fields.iteritems():
+ if isinstance(value, tuple):
+ filename, value = value
+ form.append('Content-Disposition: form-data; name="%s"; '
+ 'filename="%s"' % (name, filename))
+ else:
+ form.append('Content-Disposition: form-data; name="%s"' % name)
+ form.append('')
+ form.append(value)
+ form.append(sep)
+ form[-1] += "--"
+ body = "\r\n".join(form) + "\r\n"
+ headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
+ }
+ print "BODY", body
+ return client.getPage(url, method="POST", postdata=body,
+ headers=headers, followRedirect=False)
def shouldFail(self, res, expected_failure, which, substring=None):
print "SHOULDFAIL", res
return d
def test_GET_DIRURL(self): # YES
- d = self.GET("/vdrive/global/foo")
+ # the addSlash means we get a redirect here
+ d = self.GET("/vdrive/global/foo", followRedirect=True)
def _check(res):
self.failUnless(re.search(r'<td><a href="bar.txt">bar.txt</a></td>'
'\s+<td>FILE</td>'
d.addCallback(_check)
return d
- def test_POST_upload(self):
- form = "TODO"
- d = self.POST("/vdrive/global/foo", form)
+ def test_POST_upload(self): # YES
+ d = self.POST("/vdrive/global/foo", t="upload",
+ file=("new.txt", self.NEWFILE_CONTENTS))
+ def _check(res):
+ self.failUnless("new.txt" in self._foo_node.children)
+ new_uri = self._foo_node.children["new.txt"]
+ new_contents = self.files[new_uri]
+ self.failUnlessEqual(new_contents, self.NEWFILE_CONTENTS)
+ self.failUnlessEqual(res.strip(), new_uri)
+ d.addCallback(_check)
return d
- def test_POST_mkdir(self):
- form = "TODO"
- d = self.POST("/vdrive/global/foo", form)
+ def test_POST_upload_named(self): # YES
+ d = self.POST("/vdrive/global/foo", t="upload",
+ name="new.txt", file=self.NEWFILE_CONTENTS)
+ def _check(res):
+ self.failUnless("new.txt" in self._foo_node.children)
+ new_uri = self._foo_node.children["new.txt"]
+ new_contents = self.files[new_uri]
+ self.failUnlessEqual(new_contents, self.NEWFILE_CONTENTS)
+ self.failUnlessEqual(res.strip(), new_uri)
+ d.addCallback(_check)
return d
- def test_POST_put_uri(self):
- form = "TODO"
- d = self.POST("/vdrive/global/foo", form)
+ def test_POST_mkdir(self): # YES, return value?
+ d = self.POST("/vdrive/global/foo", t="mkdir", name="newdir")
+ def _check(res):
+ self.failUnless("newdir" in self._foo_node.children)
+ newdir_uri = self._foo_node.children["newdir"]
+ newdir_node = self.nodes[newdir_uri]
+ self.failIf(newdir_node.children)
+ d.addCallback(_check)
return d
- def test_POST_delete(self):
- form = "TODO, bar.txt"
- d = self.POST("/vdrive/global/foo", form)
+ def test_POST_put_uri(self): # YES
+ newuri = self.makefile(8)
+ contents = self.files[newuri]
+ d = self.POST("/vdrive/global/foo", t="uri", name="new.txt", uri=newuri)
+ def _check(res):
+ self.failUnless("new.txt" in self._foo_node.children)
+ new_uri = self._foo_node.children["new.txt"]
+ new_contents = self.files[new_uri]
+ self.failUnlessEqual(new_contents, contents)
+ self.failUnlessEqual(res.strip(), new_uri)
+ d.addCallback(_check)
return d
- def test_URI_GET(self):
- raise unittest.SkipTest("not yet")
- d = self.GET("/uri/%s/bar.txt" % foo_uri)
+ def test_POST_delete(self): # yes
+ d = self.POST("/vdrive/global/foo", t="delete", name="bar.txt")
+ def _check(res):
+ self.failIf("bar.txt" in self._foo_node.children)
+ d.addCallback(_check)
return d
- def test_PUT_NEWFILEURL_uri(self):
- raise unittest.SkipTest("not yet")
- d = self.PUT("/vdrive/global/foo/new.txt?uri", new_uri)
+ def shouldRedirect(self, res, target):
+ if not isinstance(res, failure.Failure):
+ self.fail("we were expecting to get redirected to %s, not get an"
+ " actual page: %s" % (target, res))
+ res.trap(error.PageRedirect)
+ # the PageRedirect does not seem to capture the uri= query arg
+ # properly, so we can't check for it.
+ print "location:", res.value.location
+ realtarget = self.webish_url + target
+ self.failUnlessEqual(res.value.location, realtarget)
+
+ def test_GET_URI_form(self): # YES
+ base = "/uri?uri=%s" % self._bar_txt_uri
+ # this is supposed to give us a redirect to /uri/$URI, plus arguments
+ targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
+ d = self.GET(base)
+ d.addBoth(self.shouldRedirect, targetbase)
+ d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
+ d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
+ d.addCallback(lambda res: self.GET(base+"&t=json"))
+ d.addBoth(self.shouldRedirect, targetbase+"?t=json")
+ d.addCallback(self.log, "about to get file by uri")
+ d.addCallback(lambda res: self.GET(base, followRedirect=True))
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(self.log, "got file by uri, about to get dir by uri")
+ d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
+ followRedirect=True))
+ d.addCallback(self.failUnlessIsFooJSON)
+ d.addCallback(self.log, "got dir by uri")
+
+ return d
+
+ def log(self, res, msg):
+ print "MSG: %s RES: %s" % (msg, res)
+ log.msg(msg)
+ return res
+
+ def test_GET_URI_URL(self): # YES
+ base = "/uri/%s" % self._bar_txt_uri
+ d = self.GET(base)
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ return d
+
+ def test_GET_URI_URL_dir(self): # YES
+ base = "/uri/%s?t=json" % self._foo_uri
+ d = self.GET(base)
+ d.addCallback(self.failUnlessIsFooJSON)
+ return d
+
+ def test_PUT_NEWFILEURL_uri(self): # YES
+ new_uri = self.makefile(8)
+ d = self.PUT("/vdrive/global/foo/new.txt?t=uri", new_uri)
+ def _check(res):
+ self.failUnless("new.txt" in self._foo_node.children)
+ new_uri = self._foo_node.children["new.txt"]
+ new_contents = self.files[new_uri]
+ self.failUnlessEqual(new_contents, self.files[new_uri])
+ self.failUnlessEqual(res.strip(), new_uri)
+ d.addCallback(_check)
return d
def test_XMLRPC(self):
self.defaultType)
class FileDownloader(resource.Resource):
- def __init__(self, name, filenode):
- self._name = name
+ def __init__(self, filenode, name):
IFileNode(filenode)
self._filenode = filenode
+ self._name = name
def render(self, req):
gte = static.getTypeAndEncoding
class POSTHandler(rend.Page):
def __init__(self, node):
self._node = node
-
# TODO: handler methods
+ def renderHTTP(self, ctx):
+ req = inevow.IRequest(ctx)
+ print "POST", req, req.args#, req.content.read()
+ #print " ", req.requestHeaders
+ #print req.__class__
+ #print req.fields
+ #print dir(req.fields)
+ print req.fields.keys()
+ t = req.fields["t"].value
+ if t == "mkdir":
+ name = req.fields["name"].value
+ print "CREATING DIR", name
+ d = self._node.create_empty_directory(name)
+ def _done(res):
+ return "directory created"
+ d.addCallback(_done)
+ return d
+ elif t == "uri":
+ name = req.fields["name"].value
+ uri = req.fields["uri"].value
+ d = self._node.set_uri(name, uri)
+ def _done(res):
+ return uri
+ d.addCallback(_done)
+ return d
+ elif t == "delete":
+ name = req.fields["name"].value
+ d = self._node.delete(name)
+ def _done(res):
+ return "thing deleted"
+ d.addCallback(_done)
+ return d
+ elif t == "upload":
+ contents = req.fields["file"]
+ name = contents.filename
+ print "filename", name
+ if "name" in req.fields:
+ name = req.fields["name"].value
+ print "NAME WAS", name
+ uploadable = upload.FileHandle(contents.file)
+ d = self._node.add_file(name, uploadable)
+ def _done(newnode):
+ print "UPLOAD DONW", name
+ return newnode.get_uri()
+ d.addCallback(_done)
+ return d
+ else:
+ print "BAD t=%s" % t
+ return "BAD t=%s" % t
+ return "nope"
class DELETEHandler(rend.Page):
def __init__(self, node, name):
d = self.get_child_at_path(path)
def file_or_dir(node):
if IFileNode.providedBy(node):
+ filename = "unknown"
+ if path:
+ filename = path[-1]
+ if "filename" in req.args:
+ filename = req.args["filename"][0]
if localfile:
# write contents to a local file
return LocalFileDownloader(node, localfile), ()
elif t == "":
# send contents as the result
print "FileDownloader"
- return FileDownloader(path[-1], node), ()
+ return FileDownloader(node, filename), ()
elif t == "json":
print "Localfilejsonmetadata"
return FileJSONMetadata(node), ()
def locateChild(self, ctx, segments):
client = IClient(ctx)
+ req = inevow.IRequest(ctx)
vdrive = client.getServiceNamed("vdrive")
print "Root.locateChild", segments
d.addCallback(lambda vd: vd.locateChild(ctx, segments[2:]))
return d
elif segments[0] == "uri":
+ print "looking at /uri", segments, req.args
+ if len(segments) == 1:
+ if "uri" in req.args:
+ uri = req.args["uri"][0]
+ print "REDIRECTING"
+ there = url.URL.fromContext(ctx)
+ there = there.clear("uri")
+ there = there.child("uri").child(uri)
+ print " TO", there
+ return there, ()
if len(segments) < 2:
return rend.NotFound
uri = segments[1]
d = vdrive.get_node(uri)
- d.addCallback(lambda node: VDrive(node), uri)
+ d.addCallback(lambda node: VDrive(node, "<from-uri>"))
d.addCallback(lambda vd: vd.locateChild(ctx, segments[2:]))
return d
elif segments[0] == "xmlrpc":
pass # TODO
elif segments[0] == "download_uri":
- req = inevow.IRequest(ctx)
dl = get_downloader_service(ctx)
filename = "unknown_filename"
if "filename" in req.args:
uri = req.args["uri"][0]
else:
return rend.NotFound
- child = FileDownloader(filename, FileNode(uri, IClient(ctx)))
+ child = FileDownloader(FileNode(uri, IClient(ctx)), filename)
return child, ()
return rend.Page.locateChild(self, ctx, segments)