source file: /home/buildslave/tahoe/edgy/build/src/allmydata/webish.py
file stats: 91 lines, 88 executed: 96.7% covered
coverage versus previous test: 0 lines added, 0 lines removed
1. import time
2. from twisted.application import service, strports, internet
3. from twisted.web import http
4. from twisted.internet import defer
5. from nevow import appserver, inevow, static
6. from allmydata.util import log
7.
8. from allmydata.web import introweb, root
9. from allmydata.web.common import IOpHandleTable, MyExceptionHandler
10.
11. # we must override twisted.web.http.Request.requestReceived with a version
12. # that doesn't use cgi.parse_multipart() . Since we actually use Nevow, we
13. # override the nevow-specific subclass, nevow.appserver.NevowRequest . This
14. # is an exact copy of twisted.web.http.Request (from SVN HEAD on 10-Aug-2007)
15. # that modifies the way form arguments are parsed. Note that this sort of
16. # surgery may induce a dependency upon a particular version of twisted.web
17.
18. parse_qs = http.parse_qs
19. class MyRequest(appserver.NevowRequest):
20. fields = None
21. def requestReceived(self, command, path, version):
22. """Called by channel when all data has been received.
23.
24. This method is not intended for users.
25. """
26. self.content.seek(0,0)
27. self.args = {}
28. self.stack = []
29.
30. self.method, self.uri = command, path
31. self.clientproto = version
32. x = self.uri.split('?', 1)
33.
34. if len(x) == 1:
35. self.path = self.uri
36. else:
37. self.path, argstring = x
38. self.args = parse_qs(argstring, 1)
39.
40. # cache the client and server information, we'll need this later to be
41. # serialized and sent with the request so CGIs will work remotely
42. self.client = self.channel.transport.getPeer()
43. self.host = self.channel.transport.getHost()
44.
45. # Argument processing.
46.
47. ## The original twisted.web.http.Request.requestReceived code parsed the
48. ## content and added the form fields it found there to self.args . It
49. ## did this with cgi.parse_multipart, which holds the arguments in RAM
50. ## and is thus unsuitable for large file uploads. The Nevow subclass
51. ## (nevow.appserver.NevowRequest) uses cgi.FieldStorage instead (putting
52. ## the results in self.fields), which is much more memory-efficient.
53. ## Since we know we're using Nevow, we can anticipate these arguments
54. ## appearing in self.fields instead of self.args, and thus skip the
55. ## parse-content-into-self.args step.
56.
57. ## args = self.args
58. ## ctype = self.getHeader('content-type')
59. ## if self.method == "POST" and ctype:
60. ## mfd = 'multipart/form-data'
61. ## key, pdict = cgi.parse_header(ctype)
62. ## if key == 'application/x-www-form-urlencoded':
63. ## args.update(parse_qs(self.content.read(), 1))
64. ## elif key == mfd:
65. ## try:
66. ## args.update(cgi.parse_multipart(self.content, pdict))
67. ## except KeyError, e:
68. ## if e.args[0] == 'content-disposition':
69. ## # Parse_multipart can't cope with missing
70. ## # content-dispostion headers in multipart/form-data
71. ## # parts, so we catch the exception and tell the client
72. ## # it was a bad request.
73. ## self.channel.transport.write(
74. ## "HTTP/1.1 400 Bad Request\r\n\r\n")
75. ## self.channel.transport.loseConnection()
76. ## return
77. ## raise
78. self.processing_started_timestamp = time.time()
79. self.process()
80.
81. def _logger(self):
82. # we build up a log string that hides most of the cap, to preserve
83. # user privacy. We retain the query args so we can identify things
84. # like t=json. Then we send it to the flog. We make no attempt to
85. # match apache formatting. TODO: when we move to DSA dirnodes and
86. # shorter caps, consider exposing a few characters of the cap, or
87. # maybe a few characters of its hash.
88. x = self.uri.split("?", 1)
89. if len(x) == 1:
90. # no query args
91. path = self.uri
92. queryargs = ""
93. else:
94. path, queryargs = x
95. # there is a form handler which redirects POST /uri?uri=FOO into
96. # GET /uri/FOO so folks can paste in non-HTTP-prefixed uris. Make
97. # sure we censor these too.
98. if queryargs.startswith("uri="):
99. queryargs = "[uri=CENSORED]"
100. queryargs = "?" + queryargs
101. if path.startswith("/uri"):
102. path = "/uri/[CENSORED].."
103. elif path.startswith("/file"):
104. path = "/file/[CENSORED].."
105. elif path.startswith("/named"):
106. path = "/named/[CENSORED].."
107.
108. uri = path + queryargs
109.
110. log.msg(format="web: %(clientip)s %(method)s %(uri)s %(code)s %(length)s",
111. clientip=self.getClientIP(),
112. method=self.method,
113. uri=uri,
114. code=self.code,
115. length=(self.sentLength or "-"),
116. facility="tahoe.webish",
117. level=log.OPERATIONAL,
118. )
119.
120.
121. class WebishServer(service.MultiService):
122. name = "webish"
123.
124. def __init__(self, client, webport, nodeurl_path=None, staticdir=None):
125. service.MultiService.__init__(self)
126. # the 'data' argument to all render() methods default to the Client
127. self.root = root.Root(client)
128. self.buildServer(webport, nodeurl_path, staticdir)
129. if self.root.child_operations:
130. self.site.remember(self.root.child_operations, IOpHandleTable)
131. self.root.child_operations.setServiceParent(self)
132.
133. def buildServer(self, webport, nodeurl_path, staticdir):
134. self.webport = webport
135. self.site = site = appserver.NevowSite(self.root)
136. self.site.requestFactory = MyRequest
137. self.site.remember(MyExceptionHandler(), inevow.ICanHandleException)
138. if staticdir:
139. self.root.putChild("static", static.File(staticdir))
140. s = strports.service(webport, site)
141. s.setServiceParent(self)
142. self.listener = s # stash it so the tests can query for the portnum
143. self._started = defer.Deferred()
144. if nodeurl_path:
145. self._started.addCallback(self._write_nodeurl_file, nodeurl_path)
146.
147. def startService(self):
148. service.MultiService.startService(self)
149. self._started.callback(None)
150.
151. def _write_nodeurl_file(self, junk, nodeurl_path):
152. # what is our webport?
153. s = self.listener
154. if isinstance(s, internet.TCPServer):
155. base_url = "http://127.0.0.1:%d/" % s._port.getHost().port
156. elif isinstance(s, internet.SSLServer):
157. base_url = "https://127.0.0.1:%d/" % s._port.getHost().port
158. else:
159. base_url = None
160. if base_url:
161. f = open(nodeurl_path, 'wb')
162. # this file is world-readable
163. f.write(base_url + "\n")
164. f.close()
165.
166. class IntroducerWebishServer(WebishServer):
167. def __init__(self, introducer, webport, nodeurl_path=None, staticdir=None):
168. service.MultiService.__init__(self)
169. self.root = introweb.IntroducerRoot(introducer)
170. self.buildServer(webport, nodeurl_path, staticdir)
171.