cms icon indicating copy to clipboard operation
cms copied to clipboard

"url" is None during error template rendering

Open andreyv opened this issue 5 years ago • 4 comments

Steps to reproduce:

  1. Assume CWS is running in multi-contest mode and listening on localhost:8888
  2. Run telnet localhost 8888
  3. Request a non-existing contest page:
GET /zzz HTTP/1.1
Host: localhost

  1. Actual output:
HTTP/1.1 404 Not Found
Server: TornadoServer/4.5.3
Content-Type: text/html; charset=UTF-8
Date: Tue, 06 Nov 2018 18:25:50 GMT
Content-Length: 224
Set-Cookie: zzz_login=""; expires=Mon, 06 Nov 2017 18:25:50 GMT; Path=/
Set-Cookie: _xsrf=2|47f2b7e0|d92adbae5745d453d5769a29a88df401|1541528750; Path=/

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">

        <title>Error 404***OUTPUT STOPS HERE***
  1. After that the server is still listening and responds to further requests on the same connection.

Environment: Ubuntu 18.04 with Python dependencies installed from the system; CMS is built from master and runs with Python 3.

We can also see that the server tries to set cookies for the non-existent contest.

andreyv avatar Nov 06 '18 18:11 andreyv

The actual problem is visible in the logs: Uncaught exception in write_error.

ContestWebServer log with the actual exception
2018-11-06 20:35:03,331 - INFO [Contest,0 12 authentication::_authenticate_request_from_cookie] Unsuccessful cookie authentication: no cookie provided
2018-11-06 20:35:03,333 - ERROR [Contest,0 12 web::send_error] Uncaught exception in write_error
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/tornado/web.py", line 1489, in _execute
    result = self.prepare()
  File "/usr/local/lib/python3.6/dist-packages/cms-1.5.dev0-py3.6.egg/cms/server/contest/handlers/contest.py", line 67, in prepare
    self.choose_contest()
  File "/usr/local/lib/python3.6/dist-packages/cms-1.5.dev0-py3.6.egg/cms/server/contest/handlers/contest.py", line 110, in choose_contest
    raise tornado.web.HTTPError(404)
tornado.web.HTTPError: HTTP 404: Not Found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/tornado/web.py", line 1037, in send_error
    self.write_error(status_code, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/cms-1.5.dev0-py3.6.egg/cms/server/contest/handlers/base.py", line 151, in write_error
    self.render("error.html", status_code=status_code, **self.r_params)
  File "/usr/local/lib/python3.6/dist-packages/cms-1.5.dev0-py3.6.egg/cms/server/contest/handlers/base.py", line 72, in render
    for chunk in t.generate(**params):
  File "/usr/lib/python3/dist-packages/jinja2/environment.py", line 1045, in generate
    yield self.environment.handle_exception(exc_info, True)
  File "/usr/lib/python3/dist-packages/jinja2/environment.py", line 780, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python3/dist-packages/jinja2/_compat.py", line 37, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.6/dist-packages/cms-1.5.dev0-py3.6.egg/cms/server/contest/templates/error.html", line 1, in top-level template code
    {% extends "base.html" %}
  File "/usr/local/lib/python3.6/dist-packages/cms-1.5.dev0-py3.6.egg/cms/server/contest/templates/base.html", line 9, in top-level template code
    <link rel="shortcut icon" href="{{ url("static", "favicon.ico") }}" />
TypeError: 'NoneType' object is not callable
2018-11-06 20:35:03,334 - WARNING [Contest,0 12 web::log_request] 404 GET /zzz (::ffff:127.0.0.1) 17.19ms
A similar failure in AdminWebServer
2018-11-23 09:47:38,999 - WARNING [Admin,0 50 web::log_exception] 403 POST /login (127.0.0.1): XSRF cookie does not match POST argument
2018-11-23 09:47:39,034 - ERROR [Admin,0 50 web::send_error] Uncaught exception in write_error
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/tornado/web.py", line 1487, in _execute
    self.check_xsrf_cookie()
  File "/usr/lib/python3/dist-packages/tornado/web.py", line 1334, in check_xsrf_cookie
    raise HTTPError(403, "XSRF cookie does not match POST argument")
tornado.web.HTTPError: HTTP 403: Forbidden (XSRF cookie does not match POST argument)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/tornado/web.py", line 1037, in send_error
    self.write_error(status_code, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/cms-1.5.dev0-py3.6.egg/cms/server/admin/handlers/base.py", line 336, in write_error
    self.render("error.html", status_code=status_code, **self.r_params)
  File "/usr/local/lib/python3.6/dist-packages/cms-1.5.dev0-py3.6.egg/cms/server/admin/handlers/base.py", line 281, in render
    for chunk in t.generate(**params):
  File "/usr/lib/python3/dist-packages/jinja2/environment.py", line 1045, in generate
    yield self.environment.handle_exception(exc_info, True)
  File "/usr/lib/python3/dist-packages/jinja2/environment.py", line 780, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python3/dist-packages/jinja2/_compat.py", line 37, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.6/dist-packages/cms-1.5.dev0-py3.6.egg/cms/server/admin/templates/error.html", line 1, in top-level template code
    {% extends "base.html" %}
  File "/usr/local/lib/python3.6/dist-packages/cms-1.5.dev0-py3.6.egg/cms/server/admin/templates/base.html", line 6, in top-level template code
    <link rel="shortcut icon" href="{{ url("static", "favicon.ico") }}" />
TypeError: 'NoneType' object is not callable
2018-11-23 09:47:39,048 - WARNING [Admin,0 50 web::log_request] 403 POST /login (127.0.0.1) 49.50ms

andreyv avatar Nov 06 '18 18:11 andreyv

The problem seems to be because when the template is rendered and render_params() is called, self.url is None. self.url is assigned in CommonRequestHandler.prepare().

Could it be that prepare() has not been called at the time the error template is rendered in write_error()?

andreyv avatar Jan 19 '19 09:01 andreyv

Indeed, on both occasions prepare() has not been called at the time the error is processed.

This change fixes the first failure:

diff --git a/cms/server/contest/handlers/contest.py b/cms/server/contest/handlers/contest.py
index be2f333a..1e91c3de 100644
--- a/cms/server/contest/handlers/contest.py
+++ b/cms/server/contest/handlers/contest.py
@@ -64,6 +64,8 @@ class ContestHandler(BaseHandler):
         self.contest_url = None
 
     def prepare(self):
+        super().prepare()
+
         self.choose_contest()
 
         if self.contest.allowed_localizations:
@@ -74,8 +76,6 @@ class ContestHandler(BaseHandler):
                 (k, v) for k, v in self.available_translations.items()
                 if k in lang_codes)
 
-        super().prepare()
-
         if self.is_multi_contest():
             self.contest_url = self.url[self.contest.name]
         else:

The XSRF error is harder because the check is called just before prepare() in Tornado code:

https://github.com/tornadoweb/tornado/blob/975e9168560cc03f6210d0d3aab10c870bd47080/tornado/web.py#L1674-L1676

I see about three ways to solve this:

  • Move self.url assignment from prepare() to __init__() (self.request is already defined at that time)
  • Add a check in or around self.render_params() to raise an exception if self.url is None. This way write_error() can catch it and fall back to a simple error page.
  • Simplify the error page or replace it with Tornado default

Thoughts?

andreyv avatar Jan 27 '19 15:01 andreyv

If I understand correctly, this is now fixed in ContestWebServer but still broken in AdminWebServer.

wil93 avatar Nov 27 '22 12:11 wil93