@@ -128,18 +128,39 @@ def _update_cache(task):
128128
129129
130130def is_browser_request (req : Request ) -> bool :
131- """Checks if a request is made by a browser like user agent.
132-
133- This heuristic is very weak, but hard for a browser to bypass- eg,
134- fetch/xhr and friends cannot alter the user-agent, but requests made with
135- an http library can stumble into this if they choose to user a browser like
136- user agent.
131+ """Best-effort detection if the request was made by a browser.
132+
133+ Uses three heuristics:
134+ 1) If the `User-Agent` header starts with 'Mozilla'. This heuristic is weak,
135+ but hard for a browser to bypass e.g., fetch/xhr and friends cannot alter the
136+ user agent, but requests made with an HTTP library can stumble into this if
137+ they choose to user a browser-like user agent. At the time of writing, all
138+ common browsers' user agents start with 'Mozilla'.
139+ 2) If any of the `Sec-Fetch-*` headers are present.
140+ 3) If any of the various CORS headers are present
137141 """
138- return req .headers ["User-Agent" ].startswith ("Mozilla" )
142+ return req .headers .get ("User-Agent" , "" ).startswith ("Mozilla" ) or any (
143+ h in req .headers
144+ for h in (
145+ # Origin and Referer are sent by browser user agents to give
146+ # information about the requesting origin
147+ "Referer" ,
148+ "Origin" ,
149+ # Sec-Fetch headers are sent with many but not all `fetch`
150+ # requests, and will eventually be sent on all requests.
151+ "Sec-Fetch-Mode" ,
152+ "Sec-Fetch-Dest" ,
153+ "Sec-Fetch-Site" ,
154+ "Sec-Fetch-User" ,
155+ # CORS headers specifying which other headers are modified
156+ "Access-Control-Request-Method" ,
157+ "Access-Control-Request-Headers" ,
158+ )
159+ )
139160
140161
141162def deny_browser_requests () -> Callable :
142- """Reject any requests that appear to be made by a browser"""
163+ """Reject any requests that appear to be made by a browser. """
143164
144165 def decorator_factory (f : Callable ) -> Callable :
145166 @functools .wraps (f )
@@ -149,6 +170,7 @@ async def decorator(self, req: Request):
149170 text = "Browser requests not allowed" ,
150171 status = aiohttp .web .HTTPMethodNotAllowed .status_code ,
151172 )
173+
152174 return await f (self , req )
153175
154176 return decorator
0 commit comments