Skip to content

Commit 75b7cd7

Browse files
prandlaveluca93
authored andcommitted
Serve task statement PDFs with Content-Disposition: inline
1 parent 7e1e38e commit 75b7cd7

File tree

5 files changed

+23
-10
lines changed

5 files changed

+23
-10
lines changed

cms/server/contest/handlers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
# Tasks
7070

7171
(r"/tasks/(.*)/description", TaskDescriptionHandler),
72-
(r"/tasks/(.*)/statements/(.*)", TaskStatementViewHandler),
72+
(r"/tasks/(.*)/statements/([^/]*)(?:/.*)?", TaskStatementViewHandler),
7373
(r"/tasks/(.*)/attachments/(.*)", TaskAttachmentViewHandler),
7474

7575
# Task submissions

cms/server/contest/handlers/task.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ def get(self, task_name: str, lang_code: str):
8585
self.sql_session.close()
8686

8787
if len(lang_code) > 0:
88-
filename = "%s (%s).pdf" % (task.name, lang_code)
88+
filename = "%s.%s.pdf" % (task.name, lang_code)
8989
else:
9090
filename = "%s.pdf" % task.name
9191

92-
self.fetch(statement, "application/pdf", filename)
92+
self.fetch(statement, "application/pdf", filename=filename, disposition="inline")
9393

9494

9595
class TaskAttachmentViewHandler(FileHandler):

cms/server/contest/templates/task_description.html

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
{% set page = "task_description" %}
44

5+
{# This macro includes the filename as the last component as a hack for chrome
6+
to display the correct page title (which it pulls from just the last
7+
component of the URL). It's completely ignored by the server. #}
8+
{% macro statement_url(lang_code) -%}
9+
{{ contest_url("tasks", task.name, "statements", lang_code, task.name + "." + lang_code + ".pdf") }}
10+
{%- endmacro %}
11+
512
{% block core %}
613

714

@@ -25,7 +32,7 @@ <h2>{% trans %}Statement{% endtrans %}</h2>
2532
<div class="row statement one_statement">
2633
<div class="span9">
2734
{% for lang_code in task.statements %}
28-
<a href="{{ contest_url("tasks", task.name, "statements", lang_code) }}" class="btn btn-large btn-success">{% trans %}Download task statement{% endtrans %}</a>
35+
<a href="{{ statement_url(lang_code) }}" target="_blank" class="btn btn-large btn-success">{% trans %}Download task statement{% endtrans %}</a>
2936
{% endfor %}
3037
</div>
3138
</div>
@@ -39,7 +46,7 @@ <h2>{% trans %}Statement{% endtrans %}</h2>
3946
</p>
4047
{% for statement in task.statements.values()|sort(attribute="language") %}
4148
{% if statement.language in task.primary_statements %}
42-
<a href="{{ contest_url("tasks", task.name, "statements", statement.language) }}" class="btn btn-large btn-success">
49+
<a href="{{ statement_url(statement.language) }}" target="_blank" class="btn btn-large btn-success">
4350
{% set language_name = statement.language|format_locale %}
4451
{% if statement.language != language_name %}
4552
{% trans lang=language_name %}Statement in <b>{{ lang }}</b>{% endtrans %}
@@ -51,7 +58,7 @@ <h2>{% trans %}Statement{% endtrans %}</h2>
5158
{% endfor %}
5259
{% for statement in task.statements.values()|sort(attribute="language") %}
5360
{% if statement.language in participation.user.preferred_languages and statement.language not in task.primary_statements %}
54-
<a href="{{ contest_url("tasks", task.name, "statements", statement.language) }}" class="btn btn-large">
61+
<a href="{{ statement_url(statement.language) }}" target="_blank" class="btn btn-large">
5562
{% set language_name = statement.language|format_locale %}
5663
{% if statement.language != language_name %}
5764
{% trans lang=language_name %}Statement in <b>{{ lang }}</b>{% endtrans %}
@@ -67,7 +74,7 @@ <h2>{% trans %}Statement{% endtrans %}</h2>
6774
<ul>
6875
{% for statement in task.statements.values()|sort(attribute="language") %}
6976
<li>
70-
<a href="{{ contest_url("tasks", task.name, "statements", statement.language) }}">
77+
<a href="{{ statement_url(statement.language) }}" target="_blank">
7178
{% set language_name = statement.language|format_locale %}
7279
{% if statement.language != language_name %}
7380
{% trans lang=language_name %}<b>{{ lang }}</b>{% endtrans %}

cms/server/file_middleware.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class FileServerMiddleware:
4646

4747
DIGEST_HEADER = "X-CMS-File-Digest"
4848
FILENAME_HEADER = "X-CMS-File-Filename"
49+
DISPOSITION_HEADER = "X-CMS-File-Disposition"
4950

5051
def __init__(self, file_cacher: FileCacher, app: Callable):
5152
"""Create an instance.
@@ -84,6 +85,7 @@ def wsgi_app(self, environ, start_response):
8485

8586
digest = original_response.headers.pop(self.DIGEST_HEADER)
8687
filename = original_response.headers.pop(self.FILENAME_HEADER, None)
88+
disposition = original_response.headers.pop(self.DISPOSITION_HEADER, "attachment")
8789
mimetype = original_response.mimetype
8890

8991
try:
@@ -102,7 +104,7 @@ def wsgi_app(self, environ, start_response):
102104
response.mimetype = mimetype
103105
if filename is not None:
104106
response.headers.add(
105-
"Content-Disposition", "attachment", filename=filename)
107+
"Content-Disposition", disposition, filename=filename)
106108
response.set_etag(digest)
107109
response.cache_control.no_cache = True
108110
response.cache_control.private = True

cms/server/util.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class FileHandlerMixin(RequestHandler):
8181
8282
"""
8383

84-
def fetch(self, digest: str, content_type: str, filename: str):
84+
def fetch(self, digest: str, content_type: str, filename: str | None = None, disposition: str | None = None):
8585
"""Serve the file with the given digest.
8686
8787
This will just add the headers required to trigger
@@ -90,10 +90,14 @@ def fetch(self, digest: str, content_type: str, filename: str):
9090
digest: the digest of the file that has to be served.
9191
content_type: the MIME type the file should be served as.
9292
filename: the name the file should be served as.
93+
disposition: value to set the Content-Disposition header to.
9394
9495
"""
9596
self.set_header(FileServerMiddleware.DIGEST_HEADER, digest)
96-
self.set_header(FileServerMiddleware.FILENAME_HEADER, filename)
97+
if filename is not None:
98+
self.set_header(FileServerMiddleware.FILENAME_HEADER, filename)
99+
if disposition is not None:
100+
self.set_header(FileServerMiddleware.DISPOSITION_HEADER, disposition)
97101
self.set_header("Content-Type", content_type)
98102
self.finish()
99103

0 commit comments

Comments
 (0)