source file: /home/buildslave/tahoe/edgy/build/src/allmydata/webish.py
file stats: 83 lines, 80 executed: 96.4% covered
   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
   7. from allmydata.util import log
   8. 
   9. from allmydata.web import introweb, root
  10. from allmydata.web.common import IClient, 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. 
 123. class WebishServer(service.MultiService):
 124.     name = "webish"
 125.     root_class = root.Root
 126. 
 127.     def __init__(self, webport, nodeurl_path=None):
 128.         service.MultiService.__init__(self)
 129.         self.webport = webport
 130.         self.root = self.root_class()
 131.         self.site = site = appserver.NevowSite(self.root)
 132.         self.site.requestFactory = MyRequest
 133.         s = strports.service(webport, site)
 134.         s.setServiceParent(self)
 135.         self.listener = s # stash it so the tests can query for the portnum
 136.         self._started = defer.Deferred()
 137.         if nodeurl_path:
 138.             self._started.addCallback(self._write_nodeurl_file, nodeurl_path)
 139. 
 140.     def startService(self):
 141.         service.MultiService.startService(self)
 142.         # to make various services available to render_* methods, we stash a
 143.         # reference to the client on the NevowSite. This will be available by
 144.         # adapting the 'context' argument to a special marker interface named
 145.         # IClient.
 146.         self.site.remember(self.parent, IClient)
 147.         # I thought you could do the same with an existing interface, but
 148.         # apparently 'ISite' does not exist
 149.         #self.site._client = self.parent
 150.         self.site.remember(MyExceptionHandler(), inevow.ICanHandleException)
 151.         self._started.callback(None)
 152. 
 153.     def _write_nodeurl_file(self, junk, nodeurl_path):
 154.         # what is our webport?
 155.         s = self.listener
 156.         if isinstance(s, internet.TCPServer):
 157.             base_url = "http://127.0.0.1:%d/" % s._port.getHost().port
 158.         elif isinstance(s, internet.SSLServer):
 159.             base_url = "https://127.0.0.1:%d/" % s._port.getHost().port
 160.         else:
 161.             base_url = None
 162.         if base_url:
 163.             f = open(nodeurl_path, 'wb')
 164.             # this file is world-readable
 165.             f.write(base_url + "\n")
 166.             f.close()
 167. 
 168. class IntroducerWebishServer(WebishServer):
 169.     root_class = introweb.IntroducerRoot