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
8 from allmydata.web import introweb, root
9 from allmydata.web.common import IClient, IOpHandleTable, MyExceptionHandler
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
18 parse_qs = http.parse_qs
19 class MyRequest(appserver.NevowRequest):
21 def requestReceived(self, command, path, version):
22 """Called by channel when all data has been received.
24 This method is not intended for users.
26 self.content.seek(0,0)
30 self.method, self.uri = command, path
31 self.clientproto = version
32 x = self.uri.split('?', 1)
37 self.path, argstring = x
38 self.args = parse_qs(argstring, 1)
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()
45 # Argument processing.
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.
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))
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()
78 self.processing_started_timestamp = time.time()
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)
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].."
108 uri = path + queryargs
110 log.msg(format="web: %(clientip)s %(method)s %(uri)s %(code)s %(length)s",
111 clientip=self.getClientIP(),
115 length=(self.sentLength or "-"),
116 facility="tahoe.webish",
117 level=log.OPERATIONAL,
121 class WebishServer(service.MultiService):
123 root_class = root.Root
125 def __init__(self, webport, nodeurl_path=None, staticdir=None):
126 service.MultiService.__init__(self)
127 self.webport = webport
128 self.root = self.root_class()
129 self.site = site = appserver.NevowSite(self.root)
130 self.site.requestFactory = MyRequest
131 if self.root.child_operations:
132 self.site.remember(self.root.child_operations, IOpHandleTable)
133 self.root.child_operations.setServiceParent(self)
135 self.root.putChild("static", static.File(staticdir))
136 s = strports.service(webport, site)
137 s.setServiceParent(self)
138 self.listener = s # stash it so the tests can query for the portnum
139 self._started = defer.Deferred()
141 self._started.addCallback(self._write_nodeurl_file, nodeurl_path)
143 def startService(self):
144 service.MultiService.startService(self)
145 # to make various services available to render_* methods, we stash a
146 # reference to the client on the NevowSite. This will be available by
147 # adapting the 'context' argument to a special marker interface named
149 self.site.remember(self.parent, IClient)
150 # I thought you could do the same with an existing interface, but
151 # apparently 'ISite' does not exist
152 #self.site._client = self.parent
153 self.site.remember(MyExceptionHandler(), inevow.ICanHandleException)
154 self._started.callback(None)
156 def _write_nodeurl_file(self, junk, nodeurl_path):
157 # what is our webport?
159 if isinstance(s, internet.TCPServer):
160 base_url = "http://127.0.0.1:%d/" % s._port.getHost().port
161 elif isinstance(s, internet.SSLServer):
162 base_url = "https://127.0.0.1:%d/" % s._port.getHost().port
166 f = open(nodeurl_path, 'wb')
167 # this file is world-readable
168 f.write(base_url + "\n")
171 class IntroducerWebishServer(WebishServer):
172 root_class = introweb.IntroducerRoot