Skip to content

Commit 5598762

Browse files
authored
fix: add more HTML validation & fixes (ietf-tools#3891)
* Update vnu.jar * Fix py2 -> py3 issue * Run pyupgrade * test: Add default-jdk to images * test: Add option to also validate HTML with vnu.jar Since it's already installed in bin. Don't do this by default, since it increases the time needed for tests by ~50%. * fix: Stop the urlizer from urlizing in linkified mailto: text * More HTML fixes * More HTML validation fixes * And more HTML fixes * Fix floating badge * Ignore unicode errors * Only URLize docs that are existing * Final fixes * Don't URLize everything during test-crawl * Feed HTML into vnu using python rather than Java to speed things up * Allow test-crawl to start vnu on a different port * Increase retry count to vnu. Restore batch size to 30. * More HTML validation fixes * Use urllib3 to make requests to vnu, since overriding requests_mock is tricky * Undo commit of unmodified file * Also urlize ftp links * Fix matching of file name * More HTML fixes * Add `is_valid_url` filter * weekday -> data-weekday * urlencode URLs * Add and use vnu_fmt_message. Bump vnu max buffer. * Simplify doc_exists * Don't add tab link to mail archive if the URL is invalid * Run urlize_ietf_docs before linkify Reduces the possibility of generating incorrect HTML * Undo superfluous change * Runner fixes * Consolidate vnu message filtering into vnu_filter_message * Correctly handle multiple persons with same name * Minimze diff * Fix HTML nits * Print source snippet in vnu_fmt_message * Only escape if there is something to escape * Fix snippet * Skip crufty old IPR declarations * Only include modal when needed. Add handles. * Fix wordwrap+linkification * Update ietf/doc/templatetags/ietf_filters.py * Update ietf/doc/templatetags/tests_ietf_filters.py * Don't right-align second column
1 parent f778058 commit 5598762

55 files changed

Lines changed: 479 additions & 609 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bin/test-crawl

Lines changed: 52 additions & 441 deletions
Large diffs are not rendered by default.

bin/vnu.jar

3.08 MB
Binary file not shown.

dev/tests/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ RUN apt-get install -qy \
2323
bash \
2424
build-essential \
2525
curl \
26+
default-jdk \
2627
docker-ce-cli \
2728
enscript \
2829
gawk \

docker/app.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ RUN apt-get install -qy \
3232
bash \
3333
build-essential \
3434
curl \
35+
default-jdk \
3536
docker-ce-cli \
3637
enscript \
3738
fish \

ietf/doc/templatetags/ietf_filters.py

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import datetime
66
import re
7+
import os
78
from urllib.parse import urljoin
89

910
from email.utils import parseaddr
@@ -17,10 +18,13 @@
1718
from django.utils.encoding import force_text
1819
from django.utils.encoding import force_str # pyflakes:ignore force_str is used in the doctests
1920
from django.urls import reverse as urlreverse
21+
from django.core.cache import cache
22+
from django.core.validators import URLValidator
23+
from django.core.exceptions import ValidationError
2024

2125
import debug # pyflakes:ignore
2226

23-
from ietf.doc.models import BallotDocEvent
27+
from ietf.doc.models import BallotDocEvent, DocAlias
2428
from ietf.doc.models import ConsensusDocEvent
2529
from ietf.utils.html import sanitize_fragment
2630
from ietf.utils import log
@@ -184,49 +188,113 @@ def rfceditor_info_url(rfcnum : str):
184188
"""Link to the RFC editor info page for an RFC"""
185189
return urljoin(settings.RFC_EDITOR_INFO_BASE_URL, f'rfc{rfcnum}')
186190

191+
192+
def doc_exists(name):
193+
"""Check whether a given document exists"""
194+
def find_unique(n):
195+
key = hash(n)
196+
found = cache.get(key)
197+
if not found:
198+
exact = DocAlias.objects.filter(name=n).first()
199+
found = exact.name if exact else "_"
200+
cache.set(key, found)
201+
return None if found == "_" else found
202+
203+
# all documents exist when tests are running
204+
if settings.SERVER_MODE == 'test':
205+
# unless we are running test-crawl, which would otherwise 404
206+
if "DJANGO_URLIZE_IETF_DOCS_PRODUCTION" not in os.environ:
207+
return True
208+
209+
# chop away extension
210+
extension_split = re.search(r"^(.+)\.(txt|ps|pdf)$", name)
211+
if extension_split:
212+
name = extension_split.group(1)
213+
214+
if find_unique(name):
215+
return True
216+
217+
# check for embedded rev - this may be ambiguous, so don't
218+
# chop it off if we don't find a match
219+
rev_split = re.search("^(.+)-([0-9]{2,})$", name)
220+
if rev_split:
221+
name = rev_split.group(1)
222+
if find_unique(name):
223+
return True
224+
225+
return False
226+
227+
228+
def link_charter_doc_match1(match):
229+
if not doc_exists(match[0]):
230+
return match[0]
231+
return f'<a href="/doc/{match[1][:-1]}/{match[2]}/">{match[0]}</a>'
232+
233+
234+
def link_charter_doc_match2(match):
235+
if not doc_exists(match[0]):
236+
return match[0]
237+
return f'<a href="/doc/{match[1][:-1]}/{match[2]}/">{match[0]}</a>'
238+
239+
187240
def link_non_charter_doc_match(match):
188-
if len(match[3])==2 and match[3].isdigit():
241+
if not doc_exists(match[0]):
242+
return match[0]
243+
if len(match[3]) == 2 and match[3].isdigit():
189244
return f'<a href="/doc/{match[2][:-1]}/{match[3]}/">{match[0]}</a>'
190245
else:
191246
return f'<a href="/doc/{match[2]}{match[3]}/">{match[0]}</a>'
192247

193-
@register.filter(name='urlize_ietf_docs', is_safe=True, needs_autoescape=True)
248+
249+
def link_other_doc_match(match):
250+
# there may be whitespace in the match
251+
doc = re.sub(r"\s+", "", match[0])
252+
if not doc_exists(doc):
253+
return match[0]
254+
return f'<a href="/doc/{match[2].strip().lower()}{match[3]}/">{match[1]}</a>'
255+
256+
257+
@register.filter(name="urlize_ietf_docs", is_safe=True, needs_autoescape=True)
194258
def urlize_ietf_docs(string, autoescape=None):
195259
"""
196260
Make occurrences of RFC NNNN and draft-foo-bar links to /doc/.
197261
"""
198262
if autoescape and not isinstance(string, SafeData):
199-
string = escape(string)
200-
exp1 = r"\b(charter-(?:[\d\w\.+]+-)*)(\d\d-\d\d)(\.txt)?\b"
201-
exp2 = r"\b(charter-(?:[\d\w\.+]+-)*)(\d\d)(\.txt)?\b"
263+
if "<" in string:
264+
string = escape(string)
265+
else:
266+
string = mark_safe(string)
267+
exp1 = r"\b(?<![/\-:=#])(charter-(?:[\d\w\.+]+-)*)(\d\d-\d\d)(\.txt)?\b"
268+
exp2 = r"\b(?<![/\-:=#])(charter-(?:[\d\w\.+]+-)*)(\d\d)(\.txt)?\b"
202269
if re.search(exp1, string):
203270
string = re.sub(
204271
exp1,
205-
lambda x: f'<a href="/doc/{x[1][:-1]}/{x[2]}/">{x[0]}</a>',
272+
link_charter_doc_match1,
206273
string,
207274
flags=re.IGNORECASE | re.ASCII,
208275
)
209-
elif re.search(exp2, string):
276+
elif re.search(exp2, string):
210277
string = re.sub(
211278
exp2,
212-
lambda x: f'<a href="/doc/{x[1][:-1]}/{x[2]}/">{x[0]}</a>',
279+
link_charter_doc_match2,
213280
string,
214281
flags=re.IGNORECASE | re.ASCII,
215282
)
216283
string = re.sub(
217-
r"\b(?<![/-])(((?:draft-|bofreq-|conflict-review-|status-change-)(?:[\d\w\.+]+-)*)([\d\w\.+]+?)(\.txt)?)\b",
284+
r"\b(?<![/\-:=#])(((?:draft-|bofreq-|conflict-review-|status-change-)(?:[\d\w\.+]+-)*)([\d\w\.+]+?)(\.txt)?)\b(?![-@])",
218285
link_non_charter_doc_match,
219286
string,
220287
flags=re.IGNORECASE | re.ASCII,
221288
)
222289
string = re.sub(
223290
# r"\b((RFC|BCP|STD|FYI|(?:draft-|bofreq-|conflict-review-|status-change-|charter-)[-\d\w.+]+)\s*0*(\d+))\b",
224-
r"\b(?<!-)((RFC|BCP|STD|FYI)\s*0*(\d+))\b",
225-
lambda x: f'<a href="/doc/{x[2].strip().lower()}{x[3]}/">{x[1]}</a>',
291+
r"\b(?<![/\-:=#])((RFC|BCP|STD|FYI)\s*0*(\d+))\b",
292+
link_other_doc_match,
226293
string,
227294
flags=re.IGNORECASE | re.ASCII,
228295
)
229296
return mark_safe(string)
297+
230298
urlize_ietf_docs = stringfilter(urlize_ietf_docs)
231299

232300
@register.filter(name='urlize_related_source_list', is_safe=True, needs_autoescape=True)
@@ -444,7 +512,7 @@ def format_snippet(text, trunc_words=25):
444512
@register.simple_tag
445513
def doc_edit_button(url_name, *args, **kwargs):
446514
"""Given URL name/args/kwargs, looks up the URL just like "url" tag and returns a properly formatted button for the document material tables."""
447-
return mark_safe('<a class="btn btn-primary btn-sm" type="button" href="%s">Edit</a>' % (urlreverse(url_name, args=args, kwargs=kwargs)))
515+
return mark_safe('<a class="btn btn-primary btn-sm" href="%s">Edit</a>' % (urlreverse(url_name, args=args, kwargs=kwargs)))
448516

449517
@register.filter
450518
def textify(text):
@@ -765,3 +833,16 @@ def absurl(viewname, **kwargs):
765833
Uses settings.IDTRACKER_BASE_URL as the base.
766834
"""
767835
return urljoin(settings.IDTRACKER_BASE_URL, urlreverse(viewname, kwargs=kwargs))
836+
837+
838+
@register.filter
839+
def is_valid_url(url):
840+
"""
841+
Check if the given URL is syntactically valid
842+
"""
843+
validate_url = URLValidator()
844+
try:
845+
validate_url(url)
846+
except ValidationError:
847+
return False
848+
return True

ietf/doc/templatetags/tests_ietf_filters.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,22 @@ def test_urlize_ietf_docs(self):
4646
),
4747
(
4848
"draft-madanapalli-nd-over-802.16-problems",
49-
'<a href="/doc/draft-madanapalli-nd-over-802.16-problems/">draft-madanapalli-nd-over-802.16-problems</a>'
49+
'<a href="/doc/draft-madanapalli-nd-over-802.16-problems/">draft-madanapalli-nd-over-802.16-problems</a>'
5050
),
5151
(
5252
"draft-madanapalli-nd-over-802.16-problems-02.txt",
53-
'<a href="/doc/draft-madanapalli-nd-over-802.16-problems/02/">draft-madanapalli-nd-over-802.16-problems-02.txt</a>'
53+
'<a href="/doc/draft-madanapalli-nd-over-802.16-problems/02/">draft-madanapalli-nd-over-802.16-problems-02.txt</a>'
54+
),
55+
(
56+
'<a href="mailto:draft-ietf-some-names@ietf.org">draft-ietf-some-names@ietf.org</a>',
57+
'<a href="mailto:draft-ietf-some-names@ietf.org">draft-ietf-some-names@ietf.org</a>',
58+
),
59+
(
60+
"http://ieee802.org/1/files/public/docs2015/cn-thaler-Qcn-draft-PAR.pdf",
61+
"http://ieee802.org/1/files/public/docs2015/cn-thaler-Qcn-draft-PAR.pdf"
5462
)
5563
]
56-
64+
5765
# Some edge cases scraped from existing old draft names
5866
for name in [
5967
# "draft-odell-8+8", # This fails since + matches the right side of \b

ietf/group/utils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from ietf.review.utils import can_manage_review_requests_for_team
2424
from ietf.utils import log
2525
from ietf.utils.history import get_history_object_for, copy_many_to_many_for_history
26+
from ietf.doc.templatetags.ietf_filters import is_valid_url
2627
from functools import reduce
2728

2829
def save_group_in_history(group):
@@ -208,7 +209,8 @@ def construct_group_menu_context(request, group, selected, group_type, others):
208209
entries.append(("Photos", urlreverse("ietf.group.views.group_photos", kwargs=kwargs)))
209210
entries.append(("Email expansions", urlreverse("ietf.group.views.email", kwargs=kwargs)))
210211
if group.list_archive.startswith("http:") or group.list_archive.startswith("https:") or group.list_archive.startswith("ftp:"):
211-
entries.append((mark_safe("List archive &raquo;"), group.list_archive))
212+
if is_valid_url(group.list_archive):
213+
entries.append((mark_safe("List archive &raquo;"), group.list_archive))
212214

213215

214216
# actions

ietf/meeting/forms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
# need to insert empty option for use in ChoiceField
3737
# countries.insert(0, ('', '-'*9 ))
38-
countries.insert(0, ('', ''))
38+
countries.insert(0, ('', '-' * 9))
3939
timezones.insert(0, ('', '-' * 9))
4040

4141
# -------------------------------------------------
@@ -827,4 +827,4 @@ def sessiondetailsformset_factory(min_num=1, max_num=3):
827827
min_num=min_num,
828828
max_num=max_num,
829829
extra=max_num, # only creates up to max_num total
830-
)
830+
)

ietf/secr/proceedings/forms.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222

2323
class RecordingForm(forms.Form):
2424
external_url = forms.URLField(label='Url')
25-
session = forms.ModelChoiceField(queryset=Session.objects,empty_label='')
25+
session = forms.ModelChoiceField(queryset=Session.objects)
26+
session.widget.attrs['class'] = "select2-field"
27+
session.widget.attrs['data-minimum-input-length'] = 0
2628

2729
def __init__(self, *args, **kwargs):
2830
self.meeting = kwargs.pop('meeting')

ietf/secr/static/js/proceedings-recording.js

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)