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.