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