]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/test_web.py
webish: make POST /uri?t=upload deposit you on an 'Upload Results' page
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / test_web.py
1 import re, os.path, urllib
2 import simplejson
3 from twisted.application import service
4 from twisted.trial import unittest
5 from twisted.internet import defer
6 from twisted.web import client, error, http
7 from twisted.python import failure, log
8 from allmydata import interfaces, provisioning, uri, webish, upload
9 from allmydata.util import fileutil
10 from allmydata.test.common import NonGridDirectoryNode, FakeCHKFileNode, FakeMutableFileNode, create_chk_filenode
11 from allmydata.interfaces import IURI, INewDirectoryURI, IReadonlyNewDirectoryURI, IFileURI, IMutableFileURI, IMutableFileNode
12
13 # create a fake uploader/downloader, and a couple of fake dirnodes, then
14 # create a webserver that works against them
15
16 class FakeIntroducerClient:
17     def get_all_connectors(self):
18         return {}
19     def get_all_connections_for(self, service_name):
20         return frozenset()
21     def get_all_peerids(self):
22         return frozenset()
23
24 class FakeClient(service.MultiService):
25     nodeid = "fake_nodeid"
26     basedir = "fake_basedir"
27     def get_versions(self):
28         return {'allmydata': "fake",
29                 'foolscap': "fake",
30                 'twisted': "fake",
31                 'zfec': "fake",
32                 }
33     introducer_furl = "None"
34     introducer_client = FakeIntroducerClient()
35     def connected_to_introducer(self):
36         return False
37
38     def create_node_from_uri(self, uri):
39         u = IURI(uri)
40         if (INewDirectoryURI.providedBy(u)
41             or IReadonlyNewDirectoryURI.providedBy(u)):
42             return NonGridDirectoryNode(self).init_from_uri(u)
43         if IFileURI.providedBy(u):
44             return FakeCHKFileNode(u, self)
45         assert IMutableFileURI.providedBy(u), u
46         return FakeMutableFileNode(self).init_from_uri(u)
47
48     def create_empty_dirnode(self):
49         n = NonGridDirectoryNode(self)
50         d = n.create()
51         d.addCallback(lambda res: n)
52         return d
53
54     def create_mutable_file(self, contents=""):
55         n = FakeMutableFileNode(self)
56         return n.create(contents)
57
58     def upload(self, uploadable):
59         d = uploadable.get_size()
60         d.addCallback(lambda size: uploadable.read(size))
61         def _got_data(datav):
62             data = "".join(datav)
63             n = create_chk_filenode(self, data)
64             results = upload.UploadResults()
65             results.uri = n.get_uri()
66             return results
67         d.addCallback(_got_data)
68         return d
69
70
71 class WebMixin(object):
72     def setUp(self):
73         self.s = FakeClient()
74         self.s.startService()
75         self.ws = s = webish.WebishServer("0")
76         s.allow_local_access(True)
77         s.setServiceParent(self.s)
78         port = s.listener._port.getHost().port
79         self.webish_url = "http://localhost:%d" % port
80
81         l = [ self.s.create_empty_dirnode() for x in range(6) ]
82         d = defer.DeferredList(l)
83         def _then(res):
84             self.public_root = res[0][1]
85             assert interfaces.IDirectoryNode.providedBy(self.public_root), res
86             self.public_url = "/uri/" + self.public_root.get_uri()
87             self.private_root = res[1][1]
88
89             foo = res[2][1]
90             self._foo_node = foo
91             self._foo_uri = foo.get_uri()
92             self._foo_readonly_uri = foo.get_readonly_uri()
93             # NOTE: we ignore the deferred on all set_uri() calls, because we
94             # know the fake nodes do these synchronously
95             self.public_root.set_uri("foo", foo.get_uri())
96
97             self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
98             foo.set_uri("bar.txt", self._bar_txt_uri)
99             foo.set_uri("empty", res[3][1].get_uri())
100             sub_uri = res[4][1].get_uri()
101             foo.set_uri("sub", sub_uri)
102             sub = self.s.create_node_from_uri(sub_uri)
103
104             _ign, n, blocking_uri = self.makefile(1)
105             foo.set_uri("blockingfile", blocking_uri)
106
107             _ign, n, baz_file = self.makefile(2)
108             sub.set_uri("baz.txt", baz_file)
109
110             _ign, n, self._bad_file_uri = self.makefile(3)
111             # this uri should not be downloadable
112             del FakeCHKFileNode.all_contents[self._bad_file_uri]
113
114             rodir = res[5][1]
115             self.public_root.set_uri("reedownlee", rodir.get_readonly_uri())
116             rodir.set_uri("nor", baz_file)
117
118             # public/
119             # public/foo/
120             # public/foo/bar.txt
121             # public/foo/blockingfile
122             # public/foo/empty/
123             # public/foo/sub/
124             # public/foo/sub/baz.txt
125             # public/reedownlee/
126             # public/reedownlee/nor
127             self.NEWFILE_CONTENTS = "newfile contents\n"
128         d.addCallback(_then)
129         return d
130
131     def makefile(self, number):
132         contents = "contents of file %s\n" % number
133         n = create_chk_filenode(self.s, contents)
134         return contents, n, n.get_uri()
135
136     def tearDown(self):
137         return self.s.stopService()
138
139     def failUnlessIsBarDotTxt(self, res):
140         self.failUnlessEqual(res, self.BAR_CONTENTS)
141
142     def failUnlessIsBarJSON(self, res):
143         data = simplejson.loads(res)
144         self.failUnless(isinstance(data, list))
145         self.failUnlessEqual(data[0], "filenode")
146         self.failUnless(isinstance(data[1], dict))
147         self.failIf("rw_uri" in data[1]) # immutable
148         self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
149         self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
150
151     def failUnlessIsFooJSON(self, res):
152         data = simplejson.loads(res)
153         self.failUnless(isinstance(data, list))
154         self.failUnlessEqual(data[0], "dirnode", res)
155         self.failUnless(isinstance(data[1], dict))
156         self.failUnless("rw_uri" in data[1]) # mutable
157         self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
158         self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
159
160         kidnames = sorted(data[1]["children"])
161         self.failUnlessEqual(kidnames,
162                              ["bar.txt", "blockingfile", "empty", "sub"])
163         kids = data[1]["children"]
164         self.failUnlessEqual(kids["sub"][0], "dirnode")
165         self.failUnlessEqual(kids["bar.txt"][0], "filenode")
166         self.failUnlessEqual(kids["bar.txt"][1]["size"], len(self.BAR_CONTENTS))
167         self.failUnlessEqual(kids["bar.txt"][1]["ro_uri"], self._bar_txt_uri)
168
169     def GET(self, urlpath, followRedirect=False):
170         url = self.webish_url + urlpath
171         return client.getPage(url, method="GET", followRedirect=followRedirect)
172
173     def PUT(self, urlpath, data):
174         url = self.webish_url + urlpath
175         return client.getPage(url, method="PUT", postdata=data)
176
177     def DELETE(self, urlpath):
178         url = self.webish_url + urlpath
179         return client.getPage(url, method="DELETE")
180
181     def POST(self, urlpath, followRedirect=False, **fields):
182         url = self.webish_url + urlpath
183         sepbase = "boogabooga"
184         sep = "--" + sepbase
185         form = []
186         form.append(sep)
187         form.append('Content-Disposition: form-data; name="_charset"')
188         form.append('')
189         form.append('UTF-8')
190         form.append(sep)
191         for name, value in fields.iteritems():
192             if isinstance(value, tuple):
193                 filename, value = value
194                 form.append('Content-Disposition: form-data; name="%s"; '
195                             'filename="%s"' % (name, filename))
196             else:
197                 form.append('Content-Disposition: form-data; name="%s"' % name)
198             form.append('')
199             form.append(str(value))
200             form.append(sep)
201         form[-1] += "--"
202         body = "\r\n".join(form) + "\r\n"
203         headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
204                    }
205         return client.getPage(url, method="POST", postdata=body,
206                               headers=headers, followRedirect=followRedirect)
207
208     def shouldFail(self, res, expected_failure, which,
209                    substring=None, response_substring=None):
210         if isinstance(res, failure.Failure):
211             res.trap(expected_failure)
212             if substring:
213                 self.failUnless(substring in str(res),
214                                 "substring '%s' not in '%s'"
215                                 % (substring, str(res)))
216             if response_substring:
217                 self.failUnless(response_substring in res.value.response,
218                                 "respose substring '%s' not in '%s'"
219                                 % (response_substring, res.value.response))
220         else:
221             self.fail("%s was supposed to raise %s, not get '%s'" %
222                       (which, expected_failure, res))
223
224     def shouldFail2(self, expected_failure, which, substring,
225                     callable, *args, **kwargs):
226         assert substring is None or isinstance(substring, str)
227         d = defer.maybeDeferred(callable, *args, **kwargs)
228         def done(res):
229             if isinstance(res, failure.Failure):
230                 res.trap(expected_failure)
231                 if substring:
232                     self.failUnless(substring in str(res),
233                                     "substring '%s' not in '%s'"
234                                     % (substring, str(res)))
235             else:
236                 self.fail("%s was supposed to raise %s, not get '%s'" %
237                           (which, expected_failure, res))
238         d.addBoth(done)
239         return d
240
241     def should404(self, res, which):
242         if isinstance(res, failure.Failure):
243             res.trap(error.Error)
244             self.failUnlessEqual(res.value.status, "404")
245         else:
246             self.fail("%s was supposed to Error(404), not get '%s'" %
247                       (which, res))
248
249     def shouldHTTPError(self, res, which, code=None, substring=None,
250                         response_substring=None):
251         if isinstance(res, failure.Failure):
252             res.trap(error.Error)
253             if code is not None:
254                 self.failUnlessEqual(res.value.status, str(code))
255             if substring:
256                 self.failUnless(substring in str(res),
257                                 "substring '%s' not in '%s'"
258                                 % (substring, str(res)))
259             if response_substring:
260                 self.failUnless(response_substring in res.value.response,
261                                 "respose substring '%s' not in '%s'"
262                                 % (response_substring, res.value.response))
263         else:
264             self.fail("%s was supposed to Error(%s), not get '%s'" %
265                       (which, code, res))
266
267     def shouldHTTPError2(self, which,
268                          code=None, substring=None, response_substring=None,
269                          callable=None, *args, **kwargs):
270         assert substring is None or isinstance(substring, str)
271         assert callable
272         d = defer.maybeDeferred(callable, *args, **kwargs)
273         d.addBoth(self.shouldHTTPError, which,
274                   code, substring, response_substring)
275         return d
276
277
278 class Web(WebMixin, unittest.TestCase):
279     def test_create(self):
280         pass
281
282     def test_welcome(self):
283         d = self.GET("/")
284         def _check(res):
285             self.failUnless('Welcome To AllMyData' in res)
286             self.failUnless('Tahoe' in res)
287
288             self.s.basedir = 'web/test_welcome'
289             fileutil.make_dirs("web/test_welcome")
290             fileutil.make_dirs("web/test_welcome/private")
291             return self.GET("/")
292         d.addCallback(_check)
293         return d
294
295     def test_provisioning_math(self):
296         self.failUnlessEqual(provisioning.binomial(10, 0), 1)
297         self.failUnlessEqual(provisioning.binomial(10, 1), 10)
298         self.failUnlessEqual(provisioning.binomial(10, 2), 45)
299         self.failUnlessEqual(provisioning.binomial(10, 9), 10)
300         self.failUnlessEqual(provisioning.binomial(10, 10), 1)
301
302     def test_provisioning(self):
303         d = self.GET("/provisioning/")
304         def _check(res):
305             self.failUnless('Tahoe Provisioning Tool' in res)
306             fields = {'filled': True,
307                       "num_users": int(50e3),
308                       "files_per_user": 1000,
309                       "space_per_user": int(1e9),
310                       "sharing_ratio": 1.0,
311                       "encoding_parameters": "3-of-10-5",
312                       "num_servers": 30,
313                       "ownership_mode": "A",
314                       "download_rate": 100,
315                       "upload_rate": 10,
316                       "delete_rate": 10,
317                       "lease_timer": 7,
318                       }
319             return self.POST("/provisioning/", **fields)
320
321         d.addCallback(_check)
322         def _check2(res):
323             self.failUnless('Tahoe Provisioning Tool' in res)
324             self.failUnless("Share space consumed: 167.01TB" in res)
325
326             fields = {'filled': True,
327                       "num_users": int(50e6),
328                       "files_per_user": 1000,
329                       "space_per_user": int(5e9),
330                       "sharing_ratio": 1.0,
331                       "encoding_parameters": "25-of-100-50",
332                       "num_servers": 30000,
333                       "ownership_mode": "E",
334                       "drive_failure_model": "U",
335                       "drive_size": 1000,
336                       "download_rate": 1000,
337                       "upload_rate": 100,
338                       "delete_rate": 100,
339                       "lease_timer": 7,
340                       }
341             return self.POST("/provisioning/", **fields)
342         d.addCallback(_check2)
343         def _check3(res):
344             self.failUnless("Share space consumed: huge!" in res)
345             fields = {'filled': True}
346             return self.POST("/provisioning/", **fields)
347         d.addCallback(_check3)
348         def _check4(res):
349             self.failUnless("Share space consumed:" in res)
350         d.addCallback(_check4)
351         return d
352
353     def test_GET_FILEURL(self):
354         d = self.GET(self.public_url + "/foo/bar.txt")
355         d.addCallback(self.failUnlessIsBarDotTxt)
356         return d
357
358     def test_GET_FILEURL_save(self):
359         d = self.GET(self.public_url + "/foo/bar.txt?save=bar.txt")
360         # TODO: look at the headers, expect a Content-Disposition: attachment
361         # header.
362         d.addCallback(self.failUnlessIsBarDotTxt)
363         return d
364
365     def test_GET_FILEURL_download(self):
366         d = self.GET(self.public_url + "/foo/bar.txt?t=download")
367         d.addCallback(self.failUnlessIsBarDotTxt)
368         return d
369
370     def test_GET_FILEURL_missing(self):
371         d = self.GET(self.public_url + "/foo/missing")
372         d.addBoth(self.should404, "test_GET_FILEURL_missing")
373         return d
374
375     def test_PUT_NEWFILEURL(self):
376         d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
377         # TODO: we lose the response code, so we can't check this
378         #self.failUnlessEqual(responsecode, 201)
379         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, "new.txt")
380         d.addCallback(lambda res:
381                       self.failUnlessChildContentsAre(self._foo_node, "new.txt",
382                                                       self.NEWFILE_CONTENTS))
383         return d
384
385     def test_PUT_NEWFILEURL_replace(self):
386         d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
387         # TODO: we lose the response code, so we can't check this
388         #self.failUnlessEqual(responsecode, 200)
389         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, "bar.txt")
390         d.addCallback(lambda res:
391                       self.failUnlessChildContentsAre(self._foo_node, "bar.txt",
392                                                       self.NEWFILE_CONTENTS))
393         return d
394
395     def test_PUT_NEWFILEURL_no_replace(self):
396         d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
397                      self.NEWFILE_CONTENTS)
398         d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
399                   "409 Conflict",
400                   "There was already a child by that name, and you asked me "
401                   "to not replace it")
402         return d
403
404     def test_PUT_NEWFILEURL_mkdirs(self):
405         d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
406         fn = self._foo_node
407         d.addCallback(self.failUnlessURIMatchesChild, fn, "newdir/new.txt")
408         d.addCallback(lambda res: self.failIfNodeHasChild(fn, "new.txt"))
409         d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, "newdir"))
410         d.addCallback(lambda res:
411                       self.failUnlessChildContentsAre(fn, "newdir/new.txt",
412                                                       self.NEWFILE_CONTENTS))
413         return d
414
415     def test_PUT_NEWFILEURL_blocked(self):
416         d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
417                      self.NEWFILE_CONTENTS)
418         d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
419                   "400 Bad Request",
420                   "cannot create directory because there is a file in the way")
421         return d
422
423     def test_DELETE_FILEURL(self):
424         d = self.DELETE(self.public_url + "/foo/bar.txt")
425         d.addCallback(lambda res:
426                       self.failIfNodeHasChild(self._foo_node, "bar.txt"))
427         return d
428
429     def test_DELETE_FILEURL_missing(self):
430         d = self.DELETE(self.public_url + "/foo/missing")
431         d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
432         return d
433
434     def test_DELETE_FILEURL_missing2(self):
435         d = self.DELETE(self.public_url + "/missing/missing")
436         d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
437         return d
438
439     def test_GET_FILEURL_json(self):
440         # twisted.web.http.parse_qs ignores any query args without an '=', so
441         # I can't do "GET /path?json", I have to do "GET /path/t=json"
442         # instead. This may make it tricky to emulate the S3 interface
443         # completely.
444         d = self.GET(self.public_url + "/foo/bar.txt?t=json")
445         d.addCallback(self.failUnlessIsBarJSON)
446         return d
447
448     def test_GET_FILEURL_json_missing(self):
449         d = self.GET(self.public_url + "/foo/missing?json")
450         d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
451         return d
452
453     def disable_local_access(self, res=None):
454         self.ws.allow_local_access(False)
455         return res
456
457     def test_GET_FILEURL_localfile(self):
458         localfile = os.path.abspath("web/GET_FILEURL_local file")
459         url = (self.public_url + "/foo/bar.txt?t=download&localfile=%s" %
460                urllib.quote(localfile))
461         fileutil.make_dirs("web")
462         d = self.GET(url)
463         def _done(res):
464             self.failUnless(os.path.exists(localfile))
465             data = open(localfile, "rb").read()
466             self.failUnlessEqual(data, self.BAR_CONTENTS)
467         d.addCallback(_done)
468         return d
469
470     def test_GET_FILEURL_localfile_disabled(self):
471         localfile = os.path.abspath("web/GET_FILEURL_local file_disabled")
472         url = (self.public_url + "/foo/bar.txt?t=download&localfile=%s" %
473                urllib.quote(localfile))
474         fileutil.make_dirs("web")
475         self.disable_local_access()
476         d = self.GET(url)
477         d.addBoth(self.shouldFail, error.Error, "localfile disabled",
478                   "403 Forbidden",
479                   "local file access is disabled")
480         return d
481
482     def test_GET_FILEURL_localfile_nonlocal(self):
483         # TODO: somehow pretend that we aren't local, and verify that the
484         # server refuses to write to local files, probably by changing the
485         # server's idea of what counts as "local".
486         old_LOCALHOST = webish.LOCALHOST
487         webish.LOCALHOST = "127.0.0.2"
488         localfile = os.path.abspath("web/GET_FILEURL_local file_nonlocal")
489         fileutil.make_dirs("web")
490         d = self.GET(self.public_url + "/foo/bar.txt?t=download&localfile=%s"
491                      % urllib.quote(localfile))
492         d.addBoth(self.shouldFail, error.Error, "localfile non-local",
493                   "403 Forbidden",
494                   "localfile= or localdir= requires a local connection")
495         def _check(res):
496             self.failIf(os.path.exists(localfile))
497         d.addCallback(_check)
498         def _reset(res):
499             webish.LOCALHOST = old_LOCALHOST
500             return res
501         d.addBoth(_reset)
502         return d
503
504     def test_GET_FILEURL_localfile_nonabsolute(self):
505         localfile = "web/nonabsolute/path"
506         fileutil.make_dirs("web/nonabsolute")
507         d = self.GET(self.public_url + "/foo/bar.txt?t=download&localfile=%s"
508                      % urllib.quote(localfile))
509         d.addBoth(self.shouldFail, error.Error, "localfile non-absolute",
510                   "403 Forbidden",
511                   "localfile= or localdir= requires an absolute path")
512         def _check(res):
513             self.failIf(os.path.exists(localfile))
514         d.addCallback(_check)
515         return d
516
517     def test_PUT_NEWFILEURL_localfile(self):
518         localfile = os.path.abspath("web/PUT_NEWFILEURL_local file")
519         url = (self.public_url + "/foo/new.txt?t=upload&localfile=%s" %
520                urllib.quote(localfile))
521         fileutil.make_dirs("web")
522         f = open(localfile, "wb")
523         f.write(self.NEWFILE_CONTENTS)
524         f.close()
525         d = self.PUT(url, "")
526         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, "new.txt")
527         d.addCallback(lambda res:
528                       self.failUnlessChildContentsAre(self._foo_node, "new.txt",
529                                                       self.NEWFILE_CONTENTS))
530         return d
531
532     def test_PUT_NEWFILEURL_localfile_missingarg(self):
533         url = self.public_url + "/foo/new.txt?t=upload"
534         d = self.shouldHTTPError2("test_PUT_NEWFILEURL_localfile_missing",
535                                   400, "Bad Request",
536                                   "t=upload requires localfile= or localdir=",
537                                   self.PUT, url, "")
538         return d
539
540     def test_PUT_NEWFILEURL_localfile_disabled(self):
541         localfile = os.path.abspath("web/PUT_NEWFILEURL_local file_disabled")
542         url = (self.public_url + "/foo/new.txt?t=upload&localfile=%s" %
543                urllib.quote(localfile))
544         fileutil.make_dirs("web")
545         f = open(localfile, "wb")
546         f.write(self.NEWFILE_CONTENTS)
547         f.close()
548         self.disable_local_access()
549         d = self.PUT(url, "")
550         d.addBoth(self.shouldFail, error.Error, "put localfile disabled",
551                   "403 Forbidden",
552                   "local file access is disabled")
553         return d
554
555     def test_PUT_NEWFILEURL_localfile_mkdirs(self):
556         localfile = os.path.abspath("web/PUT_NEWFILEURL_local file_mkdirs")
557         fileutil.make_dirs("web")
558         f = open(localfile, "wb")
559         f.write(self.NEWFILE_CONTENTS)
560         f.close()
561         d = self.PUT(self.public_url + "/foo/newdir/new.txt?t=upload&localfile=%s"
562                      % urllib.quote(localfile), "")
563         d.addCallback(self.failUnlessURIMatchesChild,
564                       self._foo_node, "newdir/new.txt")
565         d.addCallback(lambda res:
566                       self.failIfNodeHasChild(self._foo_node, "new.txt"))
567         d.addCallback(lambda res:
568                       self.failUnlessNodeHasChild(self._foo_node, "newdir"))
569         d.addCallback(lambda res:
570                       self.failUnlessChildContentsAre(self._foo_node,
571                                                       "newdir/new.txt",
572                                                       self.NEWFILE_CONTENTS))
573         return d
574
575     def test_GET_FILEURL_uri(self):
576         d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
577         def _check(res):
578             self.failUnlessEqual(res, self._bar_txt_uri)
579         d.addCallback(_check)
580         d.addCallback(lambda res:
581                       self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
582         def _check2(res):
583             # for now, for files, uris and readonly-uris are the same
584             self.failUnlessEqual(res, self._bar_txt_uri)
585         d.addCallback(_check2)
586         return d
587
588     def test_GET_FILEURL_badtype(self):
589         d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
590                                   "bad t=bogus",
591                                   self.GET,
592                                   self.public_url + "/foo/bar.txt?t=bogus")
593         return d
594
595     def test_GET_FILEURL_uri_missing(self):
596         d = self.GET(self.public_url + "/foo/missing?t=uri")
597         d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
598         return d
599
600     def test_GET_DIRURL(self):
601         # the addSlash means we get a redirect here
602         d = self.GET(self.public_url + "/foo", followRedirect=True)
603         def _check(res):
604             # the FILE reference points to a URI, but it should end in bar.txt
605             self.failUnless(re.search(r'<td>'
606                                       '<a href="[^"]+bar.txt">bar.txt</a>'
607                                       '</td>'
608                                       '\s+<td>FILE</td>'
609                                       '\s+<td>%d</td>' % len(self.BAR_CONTENTS)
610                                       , res))
611             # the DIR reference just points to a URI
612             self.failUnless(re.search(r'<td><a href="/uri/URI%3ADIR2%3A[^"]+">sub</a></td>'
613                                       '\s+<td>DIR</td>', res))
614         d.addCallback(_check)
615
616         # look at a directory which is readonly
617         d.addCallback(lambda res:
618                       self.GET(self.public_url + "/reedownlee", followRedirect=True))
619         def _check2(res):
620             self.failUnless("(readonly)" in res, res)
621             self.failIf("Upload a file" in res, res)
622         d.addCallback(_check2)
623
624         # and at a directory that contains a readonly directory
625         d.addCallback(lambda res:
626                       self.GET(self.public_url, followRedirect=True))
627         def _check3(res):
628             self.failUnless(re.search(r'<td><a href="/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
629                                       '</td>\s+<td>DIR-RO</td>', res))
630         d.addCallback(_check3)
631
632         return d
633
634     def test_GET_DIRURL_badtype(self):
635         d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
636                                   400, "Bad Request",
637                                   "bad t=bogus",
638                                   self.GET,
639                                   self.public_url + "/foo?t=bogus")
640         return d
641
642     def test_GET_DIRURL_large(self):
643         # Nevow has a problem showing more than about 192 children of a
644         # directory: it uses defer.success() and d.addCallback in a way that
645         # can make the stack grow very quickly. See ticket #237 for details.
646         # To work around this, I think we'll need to put a 'return
647         # defer.fireEventually' in our render_ method. This test is intended
648         # to trigger the bug (and eventually verify that our workaround
649         # actually works), but it isn't yet failing for me.
650
651         d = self.s.create_empty_dirnode()
652         COUNT = 200
653         def _created(dirnode):
654             entries = [ (str(i), self._foo_uri) for i in range(COUNT) ]
655             d2 = dirnode.set_uris(entries)
656             d2.addCallback(lambda res: dirnode)
657             return d2
658         d.addCallback(_created)
659
660         def _check(dirnode):
661             large_url = "/uri/" + dirnode.get_uri() + "/"
662             return self.GET(large_url)
663         d.addCallback(_check)
664
665         def _done(res):
666             m = r'<a href="/uri/URI%3ADIR2%3A[^"]+">' + ("%d" % (COUNT-1)) + r'</a>'
667             self.failUnless(re.search(m, res))
668             self.failIf("maximum recursion depth exceeded" in res)
669         d.addCallback(_done)
670         return d
671     test_GET_DIRURL_large.timeout= 240 # this hits 120-sec timeout on overloaded vm buildslaves
672
673     def test_GET_DIRURL_json(self):
674         d = self.GET(self.public_url + "/foo?t=json")
675         d.addCallback(self.failUnlessIsFooJSON)
676         return d
677
678     def test_GET_DIRURL_manifest(self):
679         d = self.GET(self.public_url + "/foo?t=manifest", followRedirect=True)
680         def _got(manifest):
681             self.failUnless("Refresh Capabilities" in manifest)
682         d.addCallback(_got)
683         return d
684
685     def test_GET_DIRURL_uri(self):
686         d = self.GET(self.public_url + "/foo?t=uri")
687         def _check(res):
688             self.failUnlessEqual(res, self._foo_uri)
689         d.addCallback(_check)
690         return d
691
692     def test_GET_DIRURL_readonly_uri(self):
693         d = self.GET(self.public_url + "/foo?t=readonly-uri")
694         def _check(res):
695             self.failUnlessEqual(res, self._foo_readonly_uri)
696         d.addCallback(_check)
697         return d
698
699     def test_PUT_NEWDIRURL(self):
700         d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
701         d.addCallback(lambda res:
702                       self.failUnlessNodeHasChild(self._foo_node, "newdir"))
703         d.addCallback(lambda res: self._foo_node.get("newdir"))
704         d.addCallback(self.failUnlessNodeKeysAre, [])
705         return d
706
707     def test_PUT_NEWDIRURL_replace(self):
708         d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
709         d.addCallback(lambda res:
710                       self.failUnlessNodeHasChild(self._foo_node, "sub"))
711         d.addCallback(lambda res: self._foo_node.get("sub"))
712         d.addCallback(self.failUnlessNodeKeysAre, [])
713         return d
714
715     def test_PUT_NEWDIRURL_no_replace(self):
716         d = self.PUT(self.public_url + "/foo/sub?t=mkdir&replace=false", "")
717         d.addBoth(self.shouldFail, error.Error, "PUT_NEWDIRURL_no_replace",
718                   "409 Conflict",
719                   "There was already a child by that name, and you asked me "
720                   "to not replace it")
721         d.addCallback(lambda res:
722                       self.failUnlessNodeHasChild(self._foo_node, "sub"))
723         d.addCallback(lambda res: self._foo_node.get("sub"))
724         d.addCallback(self.failUnlessNodeKeysAre, ["baz.txt"])
725         return d
726
727     def test_PUT_NEWDIRURL_mkdirs(self):
728         d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
729         d.addCallback(lambda res:
730                       self.failIfNodeHasChild(self._foo_node, "newdir"))
731         d.addCallback(lambda res:
732                       self.failUnlessNodeHasChild(self._foo_node, "subdir"))
733         d.addCallback(lambda res:
734                       self._foo_node.get_child_at_path("subdir/newdir"))
735         d.addCallback(self.failUnlessNodeKeysAre, [])
736         return d
737
738     def test_DELETE_DIRURL(self):
739         d = self.DELETE(self.public_url + "/foo")
740         d.addCallback(lambda res:
741                       self.failIfNodeHasChild(self.public_root, "foo"))
742         return d
743
744     def test_DELETE_DIRURL_missing(self):
745         d = self.DELETE(self.public_url + "/foo/missing")
746         d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
747         d.addCallback(lambda res:
748                       self.failUnlessNodeHasChild(self.public_root, "foo"))
749         return d
750
751     def test_DELETE_DIRURL_missing2(self):
752         d = self.DELETE(self.public_url + "/missing")
753         d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
754         return d
755
756     def test_walker(self):
757         out = []
758         def _visitor(path, node, metadata):
759             out.append((path, node))
760             return defer.succeed(None)
761         w = webish.DirnodeWalkerMixin()
762         d = w.walk(self.public_root, _visitor)
763         def _check(res):
764             names = [path for (path,node) in out]
765             self.failUnlessEqual(sorted(names),
766                                  [('foo',),
767                                   ('foo','bar.txt'),
768                                   ('foo','blockingfile'),
769                                   ('foo', 'empty'),
770                                   ('foo', 'sub'),
771                                   ('foo','sub','baz.txt'),
772                                   ('reedownlee',),
773                                   ('reedownlee', 'nor'),
774                                   ])
775             subindex = names.index( ('foo', 'sub') )
776             bazindex = names.index( ('foo', 'sub', 'baz.txt') )
777             self.failUnless(subindex < bazindex)
778             for path,node in out:
779                 if path[-1] in ('bar.txt', 'blockingfile', 'baz.txt', 'nor'):
780                     self.failUnless(interfaces.IFileNode.providedBy(node))
781                 else:
782                     self.failUnless(interfaces.IDirectoryNode.providedBy(node))
783         d.addCallback(_check)
784         return d
785
786     def test_GET_DIRURL_localdir(self):
787         localdir = os.path.abspath("web/GET_DIRURL_local dir")
788         fileutil.make_dirs("web")
789         d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
790                      urllib.quote(localdir))
791         def _check(res):
792             barfile = os.path.join(localdir, "bar.txt")
793             self.failUnless(os.path.exists(barfile))
794             data = open(barfile, "rb").read()
795             self.failUnlessEqual(data, self.BAR_CONTENTS)
796             blockingfile = os.path.join(localdir, "blockingfile")
797             self.failUnless(os.path.exists(blockingfile))
798             subdir = os.path.join(localdir, "sub")
799             self.failUnless(os.path.isdir(subdir))
800         d.addCallback(_check)
801         return d
802
803     def test_GET_DIRURL_localdir_disabled(self):
804         localdir = os.path.abspath("web/GET_DIRURL_local dir_disabled")
805         fileutil.make_dirs("web")
806         self.disable_local_access()
807         d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
808                      urllib.quote(localdir))
809         d.addBoth(self.shouldFail, error.Error, "localfile disabled",
810                   "403 Forbidden",
811                   "local file access is disabled")
812         return d
813
814     def test_GET_DIRURL_localdir_nonabsolute(self):
815         localdir = "web/nonabsolute/dir path"
816         fileutil.make_dirs("web/nonabsolute")
817         d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
818                      urllib.quote(localdir))
819         d.addBoth(self.shouldFail, error.Error, "localdir non-absolute",
820                   "403 Forbidden",
821                   "localfile= or localdir= requires an absolute path")
822         def _check(res):
823             self.failIf(os.path.exists(localdir))
824         d.addCallback(_check)
825         return d
826
827     def test_GET_DIRURL_localdir_nolocaldir(self):
828         d = self.shouldHTTPError2("GET_DIRURL_localdir_nolocaldir",
829                                   400, "Bad Request",
830                                   "t=download requires localdir=",
831                                   self.GET,
832                                   self.public_url + "/foo?t=download")
833         return d
834
835     def touch(self, localdir, filename):
836         path = os.path.join(localdir, filename)
837         f = open(path, "wb")
838         f.write("contents of %s\n" % filename)
839         f.close()
840
841     def dump_root(self):
842         print "NODEWALK"
843         w = webish.DirnodeWalkerMixin()
844         def visitor(childpath, childnode, metadata):
845             print childpath
846         d = w.walk(self.public_root, visitor)
847         return d
848
849     def failUnlessNodeKeysAre(self, node, expected_keys):
850         d = node.list()
851         def _check(children):
852             self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
853         d.addCallback(_check)
854         return d
855     def failUnlessNodeHasChild(self, node, name):
856         d = node.list()
857         def _check(children):
858             self.failUnless(name in children)
859         d.addCallback(_check)
860         return d
861     def failIfNodeHasChild(self, node, name):
862         d = node.list()
863         def _check(children):
864             self.failIf(name in children)
865         d.addCallback(_check)
866         return d
867
868     def failUnlessChildContentsAre(self, node, name, expected_contents):
869         d = node.get_child_at_path(name)
870         d.addCallback(lambda node: node.download_to_data())
871         def _check(contents):
872             self.failUnlessEqual(contents, expected_contents)
873         d.addCallback(_check)
874         return d
875
876     def failUnlessChildURIIs(self, node, name, expected_uri):
877         d = node.get_child_at_path(name)
878         def _check(child):
879             self.failUnlessEqual(child.get_uri(), expected_uri.strip())
880         d.addCallback(_check)
881         return d
882
883     def failUnlessURIMatchesChild(self, got_uri, node, name):
884         d = node.get_child_at_path(name)
885         def _check(child):
886             self.failUnlessEqual(got_uri.strip(), child.get_uri())
887         d.addCallback(_check)
888         return d
889
890     def failUnlessCHKURIHasContents(self, got_uri, contents):
891         self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
892
893     def test_PUT_NEWDIRURL_localdir(self):
894         localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir")
895         # create some files there
896         fileutil.make_dirs(os.path.join(localdir, "one"))
897         fileutil.make_dirs(os.path.join(localdir, "one/sub"))
898         fileutil.make_dirs(os.path.join(localdir, "two"))
899         fileutil.make_dirs(os.path.join(localdir, "three"))
900         self.touch(localdir, "three/foo.txt")
901         self.touch(localdir, "three/bar.txt")
902         self.touch(localdir, "zap.zip")
903
904         d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s"
905                      % urllib.quote(localdir), "")
906         pr = self.public_root
907         d.addCallback(lambda res: self.failUnlessNodeHasChild(pr, "newdir"))
908         d.addCallback(lambda res: pr.get("newdir"))
909         d.addCallback(self.failUnlessNodeKeysAre,
910                       ["one", "two", "three", "zap.zip"])
911         d.addCallback(lambda res: pr.get_child_at_path("newdir/one"))
912         d.addCallback(self.failUnlessNodeKeysAre, ["sub"])
913         d.addCallback(lambda res: pr.get_child_at_path("newdir/three"))
914         d.addCallback(self.failUnlessNodeKeysAre, ["foo.txt", "bar.txt"])
915         d.addCallback(lambda res: pr.get_child_at_path("newdir/three/bar.txt"))
916         d.addCallback(lambda barnode: barnode.download_to_data())
917         d.addCallback(lambda contents:
918                       self.failUnlessEqual(contents,
919                                            "contents of three/bar.txt\n"))
920         return d
921
922     def test_PUT_NEWDIRURL_localdir_disabled(self):
923         localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir_disabled")
924         # create some files there
925         fileutil.make_dirs(os.path.join(localdir, "one"))
926         fileutil.make_dirs(os.path.join(localdir, "one/sub"))
927         fileutil.make_dirs(os.path.join(localdir, "two"))
928         fileutil.make_dirs(os.path.join(localdir, "three"))
929         self.touch(localdir, "three/foo.txt")
930         self.touch(localdir, "three/bar.txt")
931         self.touch(localdir, "zap.zip")
932
933         self.disable_local_access()
934         d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s"
935                      % urllib.quote(localdir), "")
936         d.addBoth(self.shouldFail, error.Error, "localfile disabled",
937                   "403 Forbidden",
938                   "local file access is disabled")
939         return d
940
941     def test_PUT_NEWDIRURL_localdir_mkdirs(self):
942         localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir_mkdirs")
943         # create some files there
944         fileutil.make_dirs(os.path.join(localdir, "one"))
945         fileutil.make_dirs(os.path.join(localdir, "one/sub"))
946         fileutil.make_dirs(os.path.join(localdir, "two"))
947         fileutil.make_dirs(os.path.join(localdir, "three"))
948         self.touch(localdir, "three/foo.txt")
949         self.touch(localdir, "three/bar.txt")
950         self.touch(localdir, "zap.zip")
951
952         d = self.PUT(self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
953                      % urllib.quote(localdir),
954                      "")
955         fn = self._foo_node
956         d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, "subdir"))
957         d.addCallback(lambda res: fn.get_child_at_path("subdir/newdir"))
958         d.addCallback(self.failUnlessNodeKeysAre,
959                       ["one", "two", "three", "zap.zip"])
960         d.addCallback(lambda res: fn.get_child_at_path("subdir/newdir/one"))
961         d.addCallback(self.failUnlessNodeKeysAre, ["sub"])
962         d.addCallback(lambda res: fn.get_child_at_path("subdir/newdir/three"))
963         d.addCallback(self.failUnlessNodeKeysAre, ["foo.txt", "bar.txt"])
964         d.addCallback(lambda res:
965                       fn.get_child_at_path("subdir/newdir/three/bar.txt"))
966         d.addCallback(lambda barnode: barnode.download_to_data())
967         d.addCallback(lambda contents:
968                       self.failUnlessEqual(contents,
969                                            "contents of three/bar.txt\n"))
970         return d
971
972     def test_PUT_NEWDIRURL_localdir_missing(self):
973         localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir_missing")
974         # we do *not* create it, to trigger an error
975         url = (self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
976                % urllib.quote(localdir))
977         d = self.shouldHTTPError2("test_PUT_NEWDIRURL_localdir_missing",
978                                   400, "Bad Request",
979                                   "%s doesn't exist!" % localdir,
980                                   self.PUT, url, "")
981         return d
982
983     def test_POST_upload(self):
984         d = self.POST(self.public_url + "/foo", t="upload",
985                       file=("new.txt", self.NEWFILE_CONTENTS))
986         fn = self._foo_node
987         d.addCallback(self.failUnlessURIMatchesChild, fn, "new.txt")
988         d.addCallback(lambda res:
989                       self.failUnlessChildContentsAre(fn, "new.txt",
990                                                       self.NEWFILE_CONTENTS))
991         return d
992
993     def test_POST_upload_no_link(self):
994         d = self.POST("/uri", t="upload",
995                       file=("new.txt", self.NEWFILE_CONTENTS))
996         def _check_upload_results(page):
997             # this should be a page which describes the results of the upload
998             # that just finished.
999             self.failUnless("Upload Results:" in page)
1000             self.failUnless("URI:" in page)
1001             uri_re = re.compile("URI: <tt><span>(.*)</span>")
1002             mo = uri_re.search(page)
1003             self.failUnless(mo, page)
1004             new_uri = mo.group(1)
1005             return new_uri
1006         d.addCallback(_check_upload_results)
1007         d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1008         return d
1009
1010     def test_POST_upload_no_link_whendone(self):
1011         d = self.POST("/uri", t="upload", when_done="/",
1012                       file=("new.txt", self.NEWFILE_CONTENTS))
1013         d.addBoth(self.shouldRedirect, "/")
1014         # XXX Test that resulting welcome page has a "most recent
1015         # upload", the URI of which points to the file contents that
1016         # you just uploaded.
1017         return d
1018     test_POST_upload_no_link_whendone.todo = "Not yet implemented."
1019
1020     def test_POST_upload_no_link_mutable(self):
1021         d = self.POST("/uri", t="upload", mutable="true",
1022                       file=("new.txt", self.NEWFILE_CONTENTS))
1023         def _check(new_uri):
1024             new_uri = new_uri.strip()
1025             u = IURI(new_uri)
1026             self.failUnless(IMutableFileURI.providedBy(u))
1027             self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1028             n = self.s.create_node_from_uri(new_uri)
1029             return n.download_to_data()
1030         d.addCallback(_check)
1031         def _check2(data):
1032             self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1033         d.addCallback(_check2)
1034         return d
1035
1036     def test_POST_upload_mutable(self):
1037         # this creates a mutable file
1038         d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1039                       file=("new.txt", self.NEWFILE_CONTENTS))
1040         fn = self._foo_node
1041         d.addCallback(self.failUnlessURIMatchesChild, fn, "new.txt")
1042         d.addCallback(lambda res:
1043                       self.failUnlessChildContentsAre(fn, "new.txt",
1044                                                       self.NEWFILE_CONTENTS))
1045         d.addCallback(lambda res: self._foo_node.get("new.txt"))
1046         def _got(newnode):
1047             self.failUnless(IMutableFileNode.providedBy(newnode))
1048             self.failUnless(newnode.is_mutable())
1049             self.failIf(newnode.is_readonly())
1050             self._mutable_uri = newnode.get_uri()
1051         d.addCallback(_got)
1052
1053         # now upload it again and make sure that the URI doesn't change
1054         NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1055         d.addCallback(lambda res:
1056                       self.POST(self.public_url + "/foo", t="upload",
1057                                 mutable="true",
1058                                 file=("new.txt", NEWER_CONTENTS)))
1059         d.addCallback(self.failUnlessURIMatchesChild, fn, "new.txt")
1060         d.addCallback(lambda res:
1061                       self.failUnlessChildContentsAre(fn, "new.txt",
1062                                                       NEWER_CONTENTS))
1063         d.addCallback(lambda res: self._foo_node.get("new.txt"))
1064         def _got2(newnode):
1065             self.failUnless(IMutableFileNode.providedBy(newnode))
1066             self.failUnless(newnode.is_mutable())
1067             self.failIf(newnode.is_readonly())
1068             self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1069         d.addCallback(_got2)
1070
1071         # finally list the directory, since mutable files are displayed
1072         # differently
1073
1074         d.addCallback(lambda res:
1075                       self.GET(self.public_url + "/foo",
1076                                followRedirect=True))
1077         def _check_page(res):
1078             # TODO: assert more about the contents
1079             self.failUnless("Overwrite" in res)
1080             self.failUnless("Choose new file:" in res)
1081             return res
1082         d.addCallback(_check_page)
1083
1084         # test that clicking on the "overwrite" button works
1085         EVEN_NEWER_CONTENTS = NEWER_CONTENTS + "even newer\n"
1086         def _parse_overwrite_form_and_submit(res):
1087             OVERWRITE_FORM_RE=re.compile('<form action="([^"]*)" method="post" .*<input type="hidden" name="t" value="overwrite" /><input type="hidden" name="name" value="([^"]*)" /><input type="hidden" name="when_done" value="([^"]*)" />', re.I)
1088             mo = OVERWRITE_FORM_RE.search(res)
1089             self.failUnless(mo)
1090             formaction=mo.group(1)
1091             formname=mo.group(2)
1092             formwhendone=mo.group(3)
1093
1094             if formaction == ".":
1095                 formaction = self.public_url + "/foo"
1096             return self.POST(formaction, t="overwrite", name=formname, when_done=formwhendone, file=("new.txt", EVEN_NEWER_CONTENTS), followRedirect=False)
1097         d.addCallback(_parse_overwrite_form_and_submit)
1098         d.addBoth(self.shouldRedirect, urllib.quote(self.public_url + "/foo/"))
1099         d.addCallback(lambda res:
1100                       self.failUnlessChildContentsAre(fn, "new.txt",
1101                                                       EVEN_NEWER_CONTENTS))
1102         d.addCallback(lambda res: self._foo_node.get("new.txt"))
1103         def _got3(newnode):
1104             self.failUnless(IMutableFileNode.providedBy(newnode))
1105             self.failUnless(newnode.is_mutable())
1106             self.failIf(newnode.is_readonly())
1107             self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1108         d.addCallback(_got3)
1109
1110         return d
1111
1112     def test_POST_upload_replace(self):
1113         d = self.POST(self.public_url + "/foo", t="upload",
1114                       file=("bar.txt", self.NEWFILE_CONTENTS))
1115         fn = self._foo_node
1116         d.addCallback(self.failUnlessURIMatchesChild, fn, "bar.txt")
1117         d.addCallback(lambda res:
1118                       self.failUnlessChildContentsAre(fn, "bar.txt",
1119                                                       self.NEWFILE_CONTENTS))
1120         return d
1121
1122     def test_POST_upload_no_replace_ok(self):
1123         d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1124                       file=("new.txt", self.NEWFILE_CONTENTS))
1125         d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1126         d.addCallback(lambda res: self.failUnlessEqual(res,
1127                                                        self.NEWFILE_CONTENTS))
1128         return d
1129
1130     def test_POST_upload_no_replace_queryarg(self):
1131         d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1132                       file=("bar.txt", self.NEWFILE_CONTENTS))
1133         d.addBoth(self.shouldFail, error.Error,
1134                   "POST_upload_no_replace_queryarg",
1135                   "409 Conflict",
1136                   "There was already a child by that name, and you asked me "
1137                   "to not replace it")
1138         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1139         d.addCallback(self.failUnlessIsBarDotTxt)
1140         return d
1141
1142     def test_POST_upload_no_replace_field(self):
1143         d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1144                       file=("bar.txt", self.NEWFILE_CONTENTS))
1145         d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1146                   "409 Conflict",
1147                   "There was already a child by that name, and you asked me "
1148                   "to not replace it")
1149         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1150         d.addCallback(self.failUnlessIsBarDotTxt)
1151         return d
1152
1153     def test_POST_upload_whendone(self):
1154         d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1155                       file=("new.txt", self.NEWFILE_CONTENTS))
1156         d.addBoth(self.shouldRedirect, "/THERE")
1157         fn = self._foo_node
1158         d.addCallback(lambda res:
1159                       self.failUnlessChildContentsAre(fn, "new.txt",
1160                                                       self.NEWFILE_CONTENTS))
1161         return d
1162
1163     def test_POST_upload_named(self):
1164         fn = self._foo_node
1165         d = self.POST(self.public_url + "/foo", t="upload",
1166                       name="new.txt", file=self.NEWFILE_CONTENTS)
1167         d.addCallback(self.failUnlessURIMatchesChild, fn, "new.txt")
1168         d.addCallback(lambda res:
1169                       self.failUnlessChildContentsAre(fn, "new.txt",
1170                                                       self.NEWFILE_CONTENTS))
1171         return d
1172
1173     def test_POST_upload_named_badfilename(self):
1174         d = self.POST(self.public_url + "/foo", t="upload",
1175                       name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1176         d.addBoth(self.shouldFail, error.Error,
1177                   "test_POST_upload_named_badfilename",
1178                   "400 Bad Request",
1179                   "name= may not contain a slash",
1180                   )
1181         # make sure that nothing was added
1182         d.addCallback(lambda res:
1183                       self.failUnlessNodeKeysAre(self._foo_node,
1184                                                  ["bar.txt", "blockingfile",
1185                                                   "empty", "sub"]))
1186         return d
1187
1188     def test_POST_mkdir(self): # return value?
1189         d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1190         d.addCallback(lambda res: self._foo_node.get("newdir"))
1191         d.addCallback(self.failUnlessNodeKeysAre, [])
1192         return d
1193
1194     def test_POST_mkdir_no_parentdir_noredirect(self):
1195         d = self.POST("/uri?t=mkdir")
1196         def _after_mkdir(res):
1197             uri.NewDirectoryURI.init_from_string(res)
1198         d.addCallback(_after_mkdir)
1199         return d
1200
1201     def test_POST_mkdir_no_parentdir_redirect(self):
1202         d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1203         d.addBoth(self.shouldRedirect, None, statuscode='303')
1204         def _check_target(target):
1205             target = urllib.unquote(target)
1206             self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1207         d.addCallback(_check_target)
1208         return d
1209
1210     def test_POST_noparent_bad(self):
1211         d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1212                                   "/uri accepts only PUT, PUT?t=mkdir, "
1213                                   "POST?t=upload, and POST?t=mkdir",
1214                                   self.POST, "/uri?t=bogus")
1215         return d
1216
1217     def test_welcome_page_mkdir_button(self):
1218         # Fetch the welcome page.
1219         d = self.GET("/")
1220         def _after_get_welcome_page(res):
1221             MKDIR_BUTTON_RE=re.compile('<form action="([^"]*)" method="post".*<input type="hidden" name="t" value="([^"]*)" /><input type="hidden" name="([^"]*)" value="([^"]*)" /><input type="submit" value="Create Directory!" />', re.I)
1222             mo = MKDIR_BUTTON_RE.search(res)
1223             formaction = mo.group(1)
1224             formt = mo.group(2)
1225             formaname = mo.group(3)
1226             formavalue = mo.group(4)
1227             return (formaction, formt, formaname, formavalue)
1228         d.addCallback(_after_get_welcome_page)
1229         def _after_parse_form(res):
1230             (formaction, formt, formaname, formavalue) = res
1231             return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1232         d.addCallback(_after_parse_form)
1233         d.addBoth(self.shouldRedirect, None, statuscode='303')
1234         return d
1235
1236     def test_POST_mkdir_replace(self): # return value?
1237         d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1238         d.addCallback(lambda res: self._foo_node.get("sub"))
1239         d.addCallback(self.failUnlessNodeKeysAre, [])
1240         return d
1241
1242     def test_POST_mkdir_no_replace_queryarg(self): # return value?
1243         d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1244         d.addBoth(self.shouldFail, error.Error,
1245                   "POST_mkdir_no_replace_queryarg",
1246                   "409 Conflict",
1247                   "There was already a child by that name, and you asked me "
1248                   "to not replace it")
1249         d.addCallback(lambda res: self._foo_node.get("sub"))
1250         d.addCallback(self.failUnlessNodeKeysAre, ["baz.txt"])
1251         return d
1252
1253     def test_POST_mkdir_no_replace_field(self): # return value?
1254         d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1255                       replace="false")
1256         d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1257                   "409 Conflict",
1258                   "There was already a child by that name, and you asked me "
1259                   "to not replace it")
1260         d.addCallback(lambda res: self._foo_node.get("sub"))
1261         d.addCallback(self.failUnlessNodeKeysAre, ["baz.txt"])
1262         return d
1263
1264     def test_POST_mkdir_whendone_field(self):
1265         d = self.POST(self.public_url + "/foo",
1266                       t="mkdir", name="newdir", when_done="/THERE")
1267         d.addBoth(self.shouldRedirect, "/THERE")
1268         d.addCallback(lambda res: self._foo_node.get("newdir"))
1269         d.addCallback(self.failUnlessNodeKeysAre, [])
1270         return d
1271
1272     def test_POST_mkdir_whendone_queryarg(self):
1273         d = self.POST(self.public_url + "/foo?when_done=/THERE",
1274                       t="mkdir", name="newdir")
1275         d.addBoth(self.shouldRedirect, "/THERE")
1276         d.addCallback(lambda res: self._foo_node.get("newdir"))
1277         d.addCallback(self.failUnlessNodeKeysAre, [])
1278         return d
1279
1280     def test_POST_put_uri(self):
1281         contents, n, newuri = self.makefile(8)
1282         d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
1283         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, "new.txt")
1284         d.addCallback(lambda res:
1285                       self.failUnlessChildContentsAre(self._foo_node, "new.txt",
1286                                                       contents))
1287         return d
1288
1289     def test_POST_put_uri_replace(self):
1290         contents, n, newuri = self.makefile(8)
1291         d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
1292         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, "bar.txt")
1293         d.addCallback(lambda res:
1294                       self.failUnlessChildContentsAre(self._foo_node, "bar.txt",
1295                                                       contents))
1296         return d
1297
1298     def test_POST_put_uri_no_replace_queryarg(self):
1299         contents, n, newuri = self.makefile(8)
1300         d = self.POST(self.public_url + "/foo?replace=false", t="uri",
1301                       name="bar.txt", uri=newuri)
1302         d.addBoth(self.shouldFail, error.Error,
1303                   "POST_put_uri_no_replace_queryarg",
1304                   "409 Conflict",
1305                   "There was already a child by that name, and you asked me "
1306                   "to not replace it")
1307         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1308         d.addCallback(self.failUnlessIsBarDotTxt)
1309         return d
1310
1311     def test_POST_put_uri_no_replace_field(self):
1312         contents, n, newuri = self.makefile(8)
1313         d = self.POST(self.public_url + "/foo", t="uri", replace="false",
1314                       name="bar.txt", uri=newuri)
1315         d.addBoth(self.shouldFail, error.Error,
1316                   "POST_put_uri_no_replace_field",
1317                   "409 Conflict",
1318                   "There was already a child by that name, and you asked me "
1319                   "to not replace it")
1320         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1321         d.addCallback(self.failUnlessIsBarDotTxt)
1322         return d
1323
1324     def test_POST_delete(self):
1325         d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
1326         d.addCallback(lambda res: self._foo_node.list())
1327         def _check(children):
1328             self.failIf("bar.txt" in children)
1329         d.addCallback(_check)
1330         return d
1331
1332     def test_POST_rename_file(self):
1333         d = self.POST(self.public_url + "/foo", t="rename",
1334                       from_name="bar.txt", to_name='wibble.txt')
1335         d.addCallback(lambda res:
1336                       self.failIfNodeHasChild(self._foo_node, "bar.txt"))
1337         d.addCallback(lambda res:
1338                       self.failUnlessNodeHasChild(self._foo_node, "wibble.txt"))
1339         d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
1340         d.addCallback(self.failUnlessIsBarDotTxt)
1341         d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
1342         d.addCallback(self.failUnlessIsBarJSON)
1343         return d
1344
1345     def test_POST_rename_file_replace(self):
1346         # rename a file and replace a directory with it
1347         d = self.POST(self.public_url + "/foo", t="rename",
1348                       from_name="bar.txt", to_name='empty')
1349         d.addCallback(lambda res:
1350                       self.failIfNodeHasChild(self._foo_node, "bar.txt"))
1351         d.addCallback(lambda res:
1352                       self.failUnlessNodeHasChild(self._foo_node, "empty"))
1353         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
1354         d.addCallback(self.failUnlessIsBarDotTxt)
1355         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1356         d.addCallback(self.failUnlessIsBarJSON)
1357         return d
1358
1359     def test_POST_rename_file_no_replace_queryarg(self):
1360         # rename a file and replace a directory with it
1361         d = self.POST(self.public_url + "/foo?replace=false", t="rename",
1362                       from_name="bar.txt", to_name='empty')
1363         d.addBoth(self.shouldFail, error.Error,
1364                   "POST_rename_file_no_replace_queryarg",
1365                   "409 Conflict",
1366                   "There was already a child by that name, and you asked me "
1367                   "to not replace it")
1368         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1369         d.addCallback(self.failUnlessIsEmptyJSON)
1370         return d
1371
1372     def test_POST_rename_file_no_replace_field(self):
1373         # rename a file and replace a directory with it
1374         d = self.POST(self.public_url + "/foo", t="rename", replace="false",
1375                       from_name="bar.txt", to_name='empty')
1376         d.addBoth(self.shouldFail, error.Error,
1377                   "POST_rename_file_no_replace_field",
1378                   "409 Conflict",
1379                   "There was already a child by that name, and you asked me "
1380                   "to not replace it")
1381         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1382         d.addCallback(self.failUnlessIsEmptyJSON)
1383         return d
1384
1385     def failUnlessIsEmptyJSON(self, res):
1386         data = simplejson.loads(res)
1387         self.failUnlessEqual(data[0], "dirnode", data)
1388         self.failUnlessEqual(len(data[1]["children"]), 0)
1389
1390     def test_POST_rename_file_slash_fail(self):
1391         d = self.POST(self.public_url + "/foo", t="rename",
1392                       from_name="bar.txt", to_name='kirk/spock.txt')
1393         d.addBoth(self.shouldFail, error.Error,
1394                   "test_POST_rename_file_slash_fail",
1395                   "400 Bad Request",
1396                   "to_name= may not contain a slash",
1397                   )
1398         d.addCallback(lambda res:
1399                       self.failUnlessNodeHasChild(self._foo_node, "bar.txt"))
1400         d.addCallback(lambda res: self.POST(self.public_url, t="rename",
1401                       from_name="foo/bar.txt", to_name='george.txt'))
1402         d.addBoth(self.shouldFail, error.Error,
1403                   "test_POST_rename_file_slash_fail",
1404                   "400 Bad Request",
1405                   "from_name= may not contain a slash",
1406                   )
1407         d.addCallback(lambda res:
1408                       self.failUnlessNodeHasChild(self.public_root, "foo"))
1409         d.addCallback(lambda res:
1410                       self.failIfNodeHasChild(self.public_root, "george.txt"))
1411         d.addCallback(lambda res:
1412                       self.failUnlessNodeHasChild(self._foo_node, "bar.txt"))
1413         d.addCallback(lambda res: self.GET(self.public_url + "/foo?t=json"))
1414         d.addCallback(self.failUnlessIsFooJSON)
1415         return d
1416
1417     def test_POST_rename_dir(self):
1418         d = self.POST(self.public_url, t="rename",
1419                       from_name="foo", to_name='plunk')
1420         d.addCallback(lambda res:
1421                       self.failIfNodeHasChild(self.public_root, "foo"))
1422         d.addCallback(lambda res:
1423                       self.failUnlessNodeHasChild(self.public_root, "plunk"))
1424         d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
1425         d.addCallback(self.failUnlessIsFooJSON)
1426         return d
1427
1428     def shouldRedirect(self, res, target=None, statuscode=None):
1429         """ If target is not None then the redirection has to go to target.  If
1430         statuscode is not None then the redirection has to be accomplished with
1431         that HTTP status code."""
1432         if not isinstance(res, failure.Failure):
1433             self.fail("we were expecting to get redirected %s, not get an"
1434                       " actual page: %s" % ((target is None) and "somewhere" or ("to " + target), res))
1435         res.trap(error.PageRedirect)
1436         if statuscode is not None:
1437             self.failUnlessEqual(res.value.status, statuscode)
1438         if target is not None:
1439             # the PageRedirect does not seem to capture the uri= query arg
1440             # properly, so we can't check for it.
1441             realtarget = self.webish_url + target
1442             self.failUnlessEqual(res.value.location, realtarget)
1443         return res.value.location
1444
1445     def test_GET_URI_form(self):
1446         base = "/uri?uri=%s" % self._bar_txt_uri
1447         # this is supposed to give us a redirect to /uri/$URI, plus arguments
1448         targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
1449         d = self.GET(base)
1450         d.addBoth(self.shouldRedirect, targetbase)
1451         d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
1452         d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
1453         d.addCallback(lambda res: self.GET(base+"&t=json"))
1454         d.addBoth(self.shouldRedirect, targetbase+"?t=json")
1455         d.addCallback(self.log, "about to get file by uri")
1456         d.addCallback(lambda res: self.GET(base, followRedirect=True))
1457         d.addCallback(self.failUnlessIsBarDotTxt)
1458         d.addCallback(self.log, "got file by uri, about to get dir by uri")
1459         d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
1460                                            followRedirect=True))
1461         d.addCallback(self.failUnlessIsFooJSON)
1462         d.addCallback(self.log, "got dir by uri")
1463
1464         return d
1465
1466     def test_GET_rename_form(self):
1467         d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
1468                      followRedirect=True) # XXX [ ] todo: figure out why '.../foo' doesn't work
1469         def _check(res):
1470             self.failUnless(re.search(r'name="when_done" value=".*%s/foo/' % (urllib.quote(self.public_url),), res), (r'name="when_done" value=".*%s/foo/' % (urllib.quote(self.public_url),), res,))
1471             self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
1472         d.addCallback(_check)
1473         return d
1474
1475     def log(self, res, msg):
1476         #print "MSG: %s  RES: %s" % (msg, res)
1477         log.msg(msg)
1478         return res
1479
1480     def test_GET_URI_URL(self):
1481         base = "/uri/%s" % self._bar_txt_uri
1482         d = self.GET(base)
1483         d.addCallback(self.failUnlessIsBarDotTxt)
1484         d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
1485         d.addCallback(self.failUnlessIsBarDotTxt)
1486         d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
1487         d.addCallback(self.failUnlessIsBarDotTxt)
1488         return d
1489
1490     def test_GET_URI_URL_dir(self):
1491         base = "/uri/%s?t=json" % self._foo_uri
1492         d = self.GET(base)
1493         d.addCallback(self.failUnlessIsFooJSON)
1494         return d
1495
1496     def test_GET_URI_URL_missing(self):
1497         base = "/uri/%s" % self._bad_file_uri
1498         d = self.GET(base)
1499         d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
1500                   http.GONE, response_substring="NotEnoughPeersError")
1501         # TODO: how can we exercise both sides of WebDownloadTarget.fail
1502         # here? we must arrange for a download to fail after target.open()
1503         # has been called, and then inspect the response to see that it is
1504         # shorter than we expected.
1505         return d
1506
1507     def test_PUT_NEWFILEURL_uri(self):
1508         contents, n, new_uri = self.makefile(8)
1509         d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
1510         d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
1511         d.addCallback(lambda res:
1512                       self.failUnlessChildContentsAre(self._foo_node, "new.txt",
1513                                                       contents))
1514         return d
1515
1516     def test_PUT_NEWFILEURL_uri_replace(self):
1517         contents, n, new_uri = self.makefile(8)
1518         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
1519         d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
1520         d.addCallback(lambda res:
1521                       self.failUnlessChildContentsAre(self._foo_node, "bar.txt",
1522                                                       contents))
1523         return d
1524
1525     def test_PUT_NEWFILEURL_uri_no_replace(self):
1526         contents, n, new_uri = self.makefile(8)
1527         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
1528         d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
1529                   "409 Conflict",
1530                   "There was already a child by that name, and you asked me "
1531                   "to not replace it")
1532         return d
1533
1534     def test_PUT_NEWFILE_URI(self):
1535         file_contents = "New file contents here\n"
1536         d = self.PUT("/uri", file_contents)
1537         def _check(uri):
1538             self.failUnless(uri in FakeCHKFileNode.all_contents)
1539             self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1540                                  file_contents)
1541             return self.GET("/uri/%s" % uri)
1542         d.addCallback(_check)
1543         def _check2(res):
1544             self.failUnlessEqual(res, file_contents)
1545         d.addCallback(_check2)
1546         return d
1547
1548     def test_PUT_NEWFILE_URI_only_PUT(self):
1549         d = self.PUT("/uri?t=bogus", "")
1550         d.addBoth(self.shouldFail, error.Error,
1551                   "PUT_NEWFILE_URI_only_PUT",
1552                   "400 Bad Request",
1553                   "/uri only accepts PUT and PUT?t=mkdir")
1554         return d
1555
1556     def test_PUT_NEWFILE_URI_mutable(self):
1557         file_contents = "New file contents here\n"
1558         d = self.PUT("/uri?mutable=true", file_contents)
1559         def _check_mutable(uri):
1560             uri = uri.strip()
1561             u = IURI(uri)
1562             self.failUnless(IMutableFileURI.providedBy(u))
1563             self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1564             n = self.s.create_node_from_uri(uri)
1565             return n.download_to_data()
1566         d.addCallback(_check_mutable)
1567         def _check2_mutable(data):
1568             self.failUnlessEqual(data, file_contents)
1569         d.addCallback(_check2_mutable)
1570         return d
1571
1572         def _check(uri):
1573             self.failUnless(uri in FakeCHKFileNode.all_contents)
1574             self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1575                                  file_contents)
1576             return self.GET("/uri/%s" % uri)
1577         d.addCallback(_check)
1578         def _check2(res):
1579             self.failUnlessEqual(res, file_contents)
1580         d.addCallback(_check2)
1581         return d
1582
1583     def test_PUT_mkdir(self):
1584         d = self.PUT("/uri?t=mkdir", "")
1585         def _check(uri):
1586             n = self.s.create_node_from_uri(uri.strip())
1587             d2 = self.failUnlessNodeKeysAre(n, [])
1588             d2.addCallback(lambda res:
1589                            self.GET("/uri/%s?t=json" % uri))
1590             return d2
1591         d.addCallback(_check)
1592         d.addCallback(self.failUnlessIsEmptyJSON)
1593         return d
1594
1595     def test_POST_check(self):
1596         d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
1597         def _done(res):
1598             # this returns a string form of the results, which are probably
1599             # None since we're using fake filenodes.
1600             # TODO: verify that the check actually happened, by changing
1601             # FakeCHKFileNode to count how many times .check() has been
1602             # called.
1603             pass
1604         d.addCallback(_done)
1605         return d
1606
1607     def test_bad_method(self):
1608         url = self.webish_url + self.public_url + "/foo/bar.txt"
1609         d = self.shouldHTTPError2("test_bad_method", 404, "Not Found", None,
1610                                   client.getPage, url, method="BOGUS")
1611         return d
1612
1613     def test_short_url(self):
1614         url = self.webish_url + "/uri"
1615         d = self.shouldHTTPError2("test_short_url", 404, "Not Found", None,
1616                                   client.getPage, url, method="DELETE")
1617         return d