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