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