1414import contextlib
1515import logging
1616import time
17- from typing import Optional , Tuple , Type , Union
17+ from typing import Optional , Tuple , Union
1818
1919import attr
2020from zope .interface import implementer
2121
22- from twisted .internet .interfaces import IAddress
22+ from twisted .internet .interfaces import IAddress , IReactorTime
2323from twisted .python .failure import Failure
24+ from twisted .web .resource import IResource
2425from twisted .web .server import Request , Site
2526
2627from synapse .config .server import ListenerConfig
@@ -49,6 +50,7 @@ class SynapseRequest(Request):
4950 * Redaction of access_token query-params in __repr__
5051 * Logging at start and end
5152 * Metrics to record CPU, wallclock and DB time by endpoint.
53+ * A limit to the size of request which will be accepted
5254
5355 It also provides a method `processing`, which returns a context manager. If this
5456 method is called, the request won't be logged until the context manager is closed;
@@ -59,8 +61,9 @@ class SynapseRequest(Request):
5961 logcontext: the log context for this request
6062 """
6163
62- def __init__ (self , channel , * args , ** kw ):
64+ def __init__ (self , channel , * args , max_request_body_size = 1024 , ** kw ):
6365 Request .__init__ (self , channel , * args , ** kw )
66+ self ._max_request_body_size = max_request_body_size
6467 self .site = channel .site # type: SynapseSite
6568 self ._channel = channel # this is used by the tests
6669 self .start_time = 0.0
@@ -97,6 +100,18 @@ def __repr__(self):
97100 self .site .site_tag ,
98101 )
99102
103+ def handleContentChunk (self , data ):
104+ # we should have a `content` by now.
105+ assert self .content , "handleContentChunk() called before gotLength()"
106+ if self .content .tell () + len (data ) > self ._max_request_body_size :
107+ logger .warning (
108+ "Aborting connection from %s because the request exceeds maximum size" ,
109+ self .client ,
110+ )
111+ self .transport .abortConnection ()
112+ return
113+ super ().handleContentChunk (data )
114+
100115 @property
101116 def requester (self ) -> Optional [Union [Requester , str ]]:
102117 return self ._requester
@@ -485,29 +500,55 @@ class _XForwardedForAddress:
485500
486501class SynapseSite (Site ):
487502 """
488- Subclass of a twisted http Site that does access logging with python's
489- standard logging
503+ Synapse-specific twisted http Site
504+
505+ This does two main things.
506+
507+ First, it replaces the requestFactory in use so that we build SynapseRequests
508+ instead of regular t.w.server.Requests. All of the constructor params are really
509+ just parameters for SynapseRequest.
510+
511+ Second, it inhibits the log() method called by Request.finish, since SynapseRequest
512+ does its own logging.
490513 """
491514
492515 def __init__ (
493516 self ,
494- logger_name ,
495- site_tag ,
517+ logger_name : str ,
518+ site_tag : str ,
496519 config : ListenerConfig ,
497- resource ,
520+ resource : IResource ,
498521 server_version_string ,
499- * args ,
500- ** kwargs ,
522+ max_request_body_size : int ,
523+ reactor : Optional [ IReactorTime ] = None ,
501524 ):
502- Site .__init__ (self , resource , * args , ** kwargs )
525+ """
526+
527+ Args:
528+ logger_name: The name of the logger to use for access logs.
529+ site_tag: A tag to use for this site - mostly in access logs.
530+ config: Configuration for the HTTP listener corresponding to this site
531+ resource: The base of the resource tree to be used for serving requests on
532+ this site
533+ server_version_string: A string to present for the Server header
534+ max_request_body_size: Maximum request body length to allow before
535+ dropping the connection
536+ reactor: reactor to be used to manage connection timeouts
537+ """
538+ Site .__init__ (self , resource , reactor = reactor )
503539
504540 self .site_tag = site_tag
505541
506542 assert config .http_options is not None
507543 proxied = config .http_options .x_forwarded
508- self .requestFactory = (
509- XForwardedForRequest if proxied else SynapseRequest
510- ) # type: Type[Request]
544+ request_class = XForwardedForRequest if proxied else SynapseRequest
545+
546+ def request_factory (channel , queued ) -> Request :
547+ return request_class (
548+ channel , max_request_body_size = max_request_body_size , queued = queued
549+ )
550+
551+ self .requestFactory = request_factory # type: ignore
511552 self .access_logger = logging .getLogger (logger_name )
512553 self .server_version_string = server_version_string .encode ("ascii" )
513554
0 commit comments