fix: add more HTML validation & fixes (#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
This commit is contained in:
Lars Eggert 2022-05-03 21:55:48 +03:00 committed by GitHub
parent f778058005
commit 5598762608
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 479 additions and 609 deletions

View file

@ -3,7 +3,7 @@
# Copyright The IETF Trust 2013-2019, All Rights Reserved
import os, sys, re, datetime, argparse, traceback, json, subprocess
import os, sys, re, datetime, argparse, traceback, json
import html5lib
import random
@ -31,7 +31,7 @@ parser.add_argument('--no-follow', dest='follow', action='store_false', default=
parser.add_argument('--validator-nu', dest='validator_nu', action='store_true',
help='Use validator.nu instead of html5lib for HTML validation')
parser.add_argument('--pedantic', action='store_true',
help='Stop the crawl on the first HTML validation issue')
help='Stop the crawl on the first error or warning')
parser.add_argument('--random', action='store_true',
help='Crawl URLs randomly')
parser.add_argument('--validate-all', dest='validate_all', action='store_true', default=False,
@ -44,6 +44,7 @@ args = parser.parse_args()
# Import Django, call setup()
os.environ.setdefault("DJANGO_SETTINGS_MODULE", args.settings or "ietf.settings_testcrawl")
os.environ["DJANGO_URLIZE_IETF_DOCS_PRODUCTION"] = "1"
import django
import django.test
@ -65,6 +66,7 @@ import debug # pyflakes:ignore
from ietf.name.models import DocTypeName
from ietf.utils.html import unescape
from ietf.utils.test_utils import unicontent
from ietf.utils.test_runner import start_vnu_server, vnu_validate, vnu_fmt_message, vnu_filter_message
# --- Constants ---
@ -156,55 +158,38 @@ def check_html_valid(url, response, args):
key = re.sub("/submit/status/nnnn/[0-9a-f]+/", "/submit/status/nnnn/bar/", key)
key = re.sub("/team/[a-z0-9-]+/", "/team/foo/", key)
key = re.sub("/wg/[a-z0-9-]+/", "/wg/foo/", key)
key = re.sub("\?.*$", "", key)
key = re.sub(r"\?.*$", "", key)
for slug in doc_types:
key = re.sub("/%s-.*/"%slug, "/%s-nnnn/"%slug, key)
if not key in validated_urls:
note('Validate: %-32s: %s' % (url[:32], key))
# These URLs have known issues, skip them until those are fixed
for pattern in (
'/secr',
'admin/',
'/doc/.*/edit/info/',
'rfc542$',
'rfc776$',
'draft-leroux-pce-pcecp-interarea-reqs',
'draft-fujiwara-dnsop-resolver-update',
):
if re.search(pattern, url):
validated_urls[key] = True
log("%s blacklisted; skipping HTML validation" % url)
return
if hasattr(response, "content"):
content = response.content
else:
content = response.streaming_content
validated_urls[key] = True
if args.validator_nu:
v = subprocess.Popen(["java", "-jar", basedir + "/bin/vnu.jar",
"--format", "json", "-"],
stdin=subprocess.PIPE, stderr=subprocess.PIPE)
for m in json.loads(v.communicate(content)[1])["messages"]:
t = m["subType"] if m["type"] == "info" else m["type"]
tags.append("\n%s\tLine %d: %s" %
(t.upper(), m["lastLine"], m["message"]))
tags.append("\n\t%s" % m["extract"].replace('\n', ' '))
tags.append("\n\t%s%s" %
(" " * m["hiliteStart"], "^" * m["hiliteLength"]))
ret = vnu_validate(content, response["Content-Type"], port=8887)
assert ret
for m in json.loads(ret)["messages"]:
if "lastLine" not in m:
tag = m # just dump the raw JSON for now
else:
tag = vnu_fmt_message(url, m, content.decode())
# disregard some HTML issues that are (usually) due to invalid
# database content
if not re.search('Forbidden code point|Bad value|seamless|The first child|Duplicate ID|The first occurrence of ID', m["message"]):
if not vnu_filter_message(m, True, False):
warnings += 1
tags.append(tag)
else:
try:
parser.parse(content)
except Exception as e:
for err in parser.errors:
pos, code, data = err
tags.append(u"WARN invalid html at line, pos %s: %s" % (pos, e))
pos, _, _ = err
tags.append("WARN invalid html at line, pos {}: {}".format(pos, e))
warnings += 1
def skip_extract_from(url):
@ -219,410 +204,24 @@ def skip_extract_from(url):
def skip_url(url):
for pattern in (
"^/community/[0-9]+/remove_document/",
"^/community/personal/",
r"^/community/[0-9]+/remove_document/",
r"^/community/personal/",
# Skip most of the slow pdf composite generation urls and svg urls
"^/meeting/[0-9]+/agenda/[0-9b-z].*-drafts\\.pdf",
"^/wg/[a-z0-9-]+/deps/svg/",
# This bad url occurs in an uploaded html agenda:
r"/site/ietfdhcwg/_/rsrc/1311005436000/system/app/css/overlay.css\?cb=simple100%250150goog-ws-left",
r"/dir/tsvdir/reviews/",
r"draft-touch-msword-template-v2\.0",
r"^/meeting/[0-9]+/agenda/[0-9b-z].*-drafts\\.pdf",
r"^/wg/[a-z0-9-]+/deps/svg/",
# Skip other bad urls
r"^/dir/tsvdir/reviews/",
r"^/ipr/\d{,3}/history/",
# Skip most html conversions, not worth the time
"^/doc/html/draft-[0-9ac-z]",
"^/doc/html/draft-b[0-9b-z]",
"^/doc/pdf/draft-[0-9ac-z]",
"^/doc/pdf/draft-b[0-9b-z]",
"^/doc/html/charter-.*",
"^/doc/html/status-.*",
"^/doc/html/rfc.*",
# These will always 404, but include only those not excluded above
# r"^/doc/html/charter-ietf-cicm",
# r"^/doc/html/charter-ietf-dcon",
# r"^/doc/html/charter-ietf-fun",
# r"^/doc/html/charter-ietf-multrans",
# r"^/doc/html/charter-ietf-sdn",
# r"^/doc/html/charter-ietf-woes",
# r"^/doc/html/draft-allan-mpls-loadbal-06",
# r"^/doc/html/draft-allocchio-mail11-00",
# r"^/doc/html/draft-almquist-leak-01",
# r"^/doc/html/draft-almquist-nexthop-01",
# r"^/doc/html/draft-armitage-ion-mars-scsp-06",
r"^/doc/html/draft-balakrishnan-cm-03",
r"^/doc/html/draft-ballardie-cbt-02",
# r"^/doc/html/draft-bellovin-ipng-shape-of-bits-00",
# r"^/doc/html/draft-bellovin-itrace-04",
# r"^/doc/html/draft-bhaskar-pim-ss-04",
# r"^/doc/html/draft-bhattach-diot-pimso-04",
# r"^/doc/html/draft-bierman-rmonmib-apmcaps-04",
# r"^/doc/html/draft-blaze-ipsp-trustmgt-04",
# r"^/doc/html/draft-blumenthal-snmpv2a-community-00",
# r"^/doc/html/draft-borenstein-agc-spec-00",
# r"^/doc/html/draft-borenstein-kidcode-00",
# r"^/doc/html/draft-borenstein-mailcap-00",
# r"^/doc/html/draft-borenstein-pgp-mime-00",
# r"^/doc/html/draft-brockhaus-lamps-cmp-updates-00",
# r"^/doc/html/draft-brockhaus-lamps-cmp-updates-03",
# r"^/doc/html/draft-brockhaus-lamps-lightweight-cmp-profile-00",
# r"^/doc/html/draft-brockhaus-lamps-lightweight-cmp-profile-03",
# r"^/doc/html/draft-brown-supplpkgs-04",
# r"^/doc/html/draft-brownlee-acct-arch-report-03",
# r"^/doc/html/draft-cain-igmp-00",
# r"^/doc/html/draft-calhoun-aaa-diameter-comp-01",
# r"^/doc/html/draft-calhoun-mobileip-fa-tokens-04",
# r"^/doc/html/draft-calhoun-mobileip-min-lat-handoff-02",
# r"^/doc/html/draft-callon-addflow-support-clnp-00",
# r"^/doc/html/draft-callon-routing-00",
# r"^/doc/html/draft-carpenter-ipng-nosi-00",
# r"^/doc/html/draft-carpenter-percep-00",
# r"^/doc/html/draft-casati-gtp-03",
# r"^/doc/html/draft-ceuppens-mpls-optical-04",
# r"^/doc/html/draft-chapin-clnp-ISO8473-00",
# r"^/doc/html/draft-cheng-modular-ikmp-00",
# r"^/doc/html/draft-cole-appm-01",
# r"^/doc/html/draft-conta-ipv6-icmp-igmp-00",
# r"^/doc/html/draft-coya-test-01",
# r"^/doc/html/draft-crocker-cidrd-myth-00",
# r"^/doc/html/draft-crocker-pci-00",
# r"^/doc/html/draft-crocker-stif-00",
# r"^/doc/html/draft-daigle-napstr-05",
# r"^/doc/html/draft-davie-intserv-compress-01",
# r"^/doc/html/draft-davie-tag-switching-atm-04",
# r"^/doc/html/draft-davin-qosrep-00",
# r"^/doc/html/draft-davin-rsvfms-00",
# r"^/doc/html/draft-dckmtr-proxy-00",
# r"^/doc/html/draft-doolan-tdp-spec-04",
# r"^/doc/html/draft-duerst-ruby-04",
# r"^/doc/html/draft-dusse-pem-message-00",
# r"^/doc/html/draft-dutt-rap-rsvp-proxy-01",
# r"^/doc/html/draft-eastlake-muse-05",
# r"^/doc/html/draft-elmalki-soliman-hmipv4v6-04",
# r"^/doc/html/draft-ema-vpim-simplev3-02",
# r"^/doc/html/draft-esaki-co-cl-ip-forw-atm-01",
# r"^/doc/html/draft-etf-marid-protocol-00",
# r"^/doc/html/draft-faltstrom-macmime-00",
# r"^/doc/html/draft-faltstrom-whois-05",
# r"^/doc/html/draft-fielding-http-spec-01",
# r"^/doc/html/draft-flick-interfaces-mib-00",
# r"^/doc/html/draft-flick-repeater-dev-mib-00",
# r"^/doc/html/draft-floyd-cc-alt",
# r"^/doc/html/draft-ford-bigten-bt-format-00",
# r"^/doc/html/draft-ford-sdrp-sipp16-format-00",
# r"^/doc/html/draft-freed-ftbp-00",
# r"^/doc/html/draft-freed-newenc-01",
# r"^/doc/html/draft-gellens-imapext-annotate-01",
# r"^/doc/html/draft-gharai-hdtv-video-04",
# r"^/doc/html/draft-glenn-layer-security-00",
# r"^/doc/html/draft-hares-idrp-familytree-00",
# r"^/doc/html/draft-harrington-control-mib-00",
# r"^/doc/html/draft-harrington-data-filter-mib-00",
# r"^/doc/html/draft-haskin-intra-route-server-00",
# r"^/doc/html/draft-helenius-ppp-subnet-04",
# r"^/doc/html/draft-hinden-ipng-addr-00",
# r"^/doc/html/draft-holbrook-ssm-04",
# r"^/doc/html/draft-hollenbeck-epp-tcp-02",
# r"^/doc/html/draft-houldsworth-ip6-nsap-use-00",
# r"^/doc/html/draft-houldsworth-sc6-hot-finland-00",
# r"^/doc/html/draft-huitema-shipworm-01",
# r"^/doc/html/draft-iab-liaisons-00",
# r"^/doc/html/draft-iab-mou2jtc1-03",
# r"^/doc/html/draft-iab-standards-processv3-00",
# r"^/doc/html/draft-ietf-aft-socks-md5-auth-00",
# r"^/doc/html/draft-ietf-bgpdepl-minutes-93feb-00",
# r"^/doc/html/draft-ietf-bmwg-overallperf-00",
# r"^/doc/html/draft-ietf-bridge-sr-obj-00",
# r"^/doc/html/draft-ietf-cat-altftp-00",
# r"^/doc/html/draft-ietf-cip-apisocket-00",
# r"^/doc/html/draft-ietf-cipso-ipsec-option-00",
# r"^/doc/html/draft-ietf-decnetiv-mib-implement-00",
# r"^/doc/html/draft-ietf-dhc-problem-stmt-00",
# r"^/doc/html/draft-ietf-dns-idpr-02",
# r"^/doc/html/draft-ietf-dns-ixfr-01",
# r"^/doc/html/draft-ietf-dnsind-dynDNS-arch-00",
# r"^/doc/html/draft-ietf-dnsind-dynDNS-impl-00",
# r"^/doc/html/draft-ietf-dtn-tcpclv4-00",
# r"^/doc/html/draft-ietf-dtn-tcpclv4-15",
# r"^/doc/html/draft-ietf-dtn-tcpclv4-18",
# r"^/doc/html/draft-ietf-dtn-tcpclv4-19",
# r"^/doc/html/draft-ietf-ethermib-objects-00",
# r"^/doc/html/draft-ietf-fax-tiff-f-reg-01",
# r"^/doc/html/draft-ietf-geopriv-dhcp-lo-option-01",
# r"^/doc/html/draft-ietf-html-charset-harmful-00",
# r"^/doc/html/draft-ietf-iafa-templates-00",
# r"^/doc/html/draft-ietf-idmr-mtree-00",
# r"^/doc/html/draft-ietf-idmr-pim-dense-spec-00",
# r"^/doc/html/draft-ietf-idmr-pim-dm-spec-08",
# r"^/doc/html/draft-ietf-idr-bgp-tcp-md5bad-01",
# r"^/doc/html/draft-ietf-idr-community-00",
# r"^/doc/html/draft-ietf-idr-rifs-00",
# r"^/doc/html/draft-ietf-ids-iwps-design-spec-01",
# r"^/doc/html/draft-ietf-ids-pilots-00",
# r"^/doc/html/draft-ietf-iesg-evolutionplan-00",
# r"^/doc/html/draft-ietf-iiir-html-01",
# r"^/doc/html/draft-ietf-iiir-http-00",
# r"^/doc/html/draft-ietf-ipae-new-ip-00",
# r"^/doc/html/draft-ietf-ipidrp-sip-01",
# r"^/doc/html/draft-ietf-iplpdn-multi-isdn-02",
# r"^/doc/html/draft-ietf-iplpdn-para-negotiation-02",
# r"^/doc/html/draft-ietf-iplpdn-shortcutrouting-02",
# r"^/doc/html/draft-ietf-iplpdn-simple-multi-01",
# r"^/doc/html/draft-ietf-ipp-indp-04",
# r"^/doc/html/draft-ietf-ipsec-ike-base-mode-03",
# r"^/doc/html/draft-ietf-ipsec-intragkm-03",
# r"^/doc/html/draft-ietf-ipsp-spsl-04",
# r"^/doc/html/draft-ietf-ipsra-pic-07",
# r"^/doc/html/draft-ietf-isis-atipx-00",
# r"^/doc/html/draft-ietf-isis-multilevel-routing-00",
# r"^/doc/html/draft-ietf-isis-nbma-00",
# r"^/doc/html/draft-ietf-isis-tcpip-01",
# r"^/doc/html/draft-ietf-isn-aup-01",
# r"^/doc/html/draft-ietf-lsma-scenarios-03",
# r"^/doc/html/draft-ietf-mailext-lang-char-00",
# r"^/doc/html/draft-ietf-mhsds-822dir-03",
# r"^/doc/html/draft-ietf-mhsds-convert-01",
# r"^/doc/html/draft-ietf-mhsds-mhsprofile-04",
# r"^/doc/html/draft-ietf-mhsds-mhsuse-03",
# r"^/doc/html/draft-ietf-mmusic-agree-00",
# r"^/doc/html/draft-ietf-mobileip-aaa-req-00",
# r"^/doc/html/draft-ietf-mobileip-addr-ext-00",
# r"^/doc/html/draft-ietf-mobileip-integrated-00",
# r"^/doc/html/draft-ietf-mobileip-mib-fa-01",
# r"^/doc/html/draft-ietf-mobileip-mib-ha-01",
# r"^/doc/html/draft-ietf-mobileip-mib-mn-01",
# r"^/doc/html/draft-ietf-mobileip-mib-sec-01",
# r"^/doc/html/draft-ietf-msi-api-03",
# r"^/doc/html/draft-ietf-nasreq-nasrequirements-01",
# r"^/doc/html/draft-ietf-netdata-implement-03",
# r"^/doc/html/draft-ietf-netdata-netdata-04",
# r"^/doc/html/draft-ietf-nimrod-dns-01",
# r"^/doc/html/draft-ietf-nisi-nicdoc-00",
# r"^/doc/html/draft-ietf-nisi-nics-00",
# r"^/doc/html/draft-ietf-nntp-news-01",
# r"^/doc/html/draft-ietf-oncrpc-remote-06",
# r"^/doc/html/draft-ietf-osids-dirtree-00",
# r"^/doc/html/draft-ietf-osids-dsanaming-02",
# r"^/doc/html/draft-ietf-osids-requirements-00",
# r"^/doc/html/draft-ietf-osids-simple-stack-00",
# r"^/doc/html/draft-ietf-osids-treestructure-00",
# r"^/doc/html/draft-ietf-osinsap-format-01",
# r"^/doc/html/draft-ietf-osix500-directories-01",
# r"^/doc/html/draft-ietf-ospf-extattr-00",
# r"^/doc/html/draft-ietf-ospf-ipv6-ext-00",
# r"^/doc/html/draft-ietf-ospf-pmp-if-00",
# r"^/doc/html/draft-ietf-otp-ver-03",
# r"^/doc/html/draft-ietf-pana-aaa-interworking-00",
# r"^/doc/html/draft-ietf-pem-notary-00",
# r"^/doc/html/draft-ietf-pim-ipv6-04",
# r"^/doc/html/draft-ietf-pim-simplekmp-02",
# r"^/doc/html/draft-ietf-pint-conf-02",
# r"^/doc/html/draft-ietf-pip-vector-00",
# r"^/doc/html/draft-ietf-poised-nomcomm-00",
# r"^/doc/html/draft-ietf-pppext-aha-auth-00",
# r"^/doc/html/draft-ietf-pppext-ipcp-network-04",
# r"^/doc/html/draft-ietf-pppext-kap-auth-00",
# r"^/doc/html/draft-ietf-pppext-kapv4-auth-00",
# r"^/doc/html/draft-ietf-ripv2-ripng-00",
# r"^/doc/html/draft-ietf-rmon-trap-00",
# r"^/doc/html/draft-ietf-rmonmib-rmon2hc-01",
# r"^/doc/html/draft-ietf-roamops-actng-08",
# r"^/doc/html/draft-ietf-rohc-rtp-rocco-performance-01",
# r"^/doc/html/draft-ietf-rohc-rtp-rocco-video-01",
# r"^/doc/html/draft-ietf-rolc-nhrp-mib-00",
# r"^/doc/html/draft-ietf-rreq-iprouters-04",
# r"^/doc/html/draft-ietf-rsvp-policy-ext-05",
# r"^/doc/html/draft-ietf-rsvp-state-compression-04",
# r"^/doc/html/draft-ietf-sdr-IPv6-pack-00",
# r"^/doc/html/draft-ietf-sdr-pl-00",
# r"^/doc/html/draft-ietf-sdr-route-construction-01",
# r"^/doc/html/draft-ietf-sdr-route-setup-00",
# r"^/doc/html/draft-ietf-sdr-speakers-attribute-00",
# r"^/doc/html/draft-ietf-sip-64bit-plan-00",
# r"^/doc/html/draft-ietf-sip-dnss-00",
# r"^/doc/html/draft-ietf-sip-ospf-00",
# r"^/doc/html/draft-ietf-sip-rip-01",
# r"^/doc/html/draft-ietf-sip-unicast-addr-00",
# r"^/doc/html/draft-ietf-sipp-auto-addr-00",
# r"^/doc/html/draft-ietf-sipp-bsd-api-02",
# r"^/doc/html/draft-ietf-sipp-dhcpopt-01",
# r"^/doc/html/draft-ietf-sipp-discovery-04",
# r"^/doc/html/draft-ietf-sipp-discovery-formats-00",
# r"^/doc/html/draft-ietf-sipp-dns-01",
# r"^/doc/html/draft-ietf-sipp-dns-ext-00",
# r"^/doc/html/draft-ietf-sipp-icmp-igmp-00",
# r"^/doc/html/draft-ietf-sipp-routing-addr-02",
# r"^/doc/html/draft-ietf-sipp-sst-overview-00",
# r"^/doc/html/draft-ietf-sipping-overload-design",
# r"^/doc/html/draft-ietf-smime-certdist-06",
# r"^/doc/html/draft-ietf-smtpext-pipeline-02",
# r"^/doc/html/draft-ietf-snmp-isdn-cisco-00",
# r"^/doc/html/draft-ietf-snmpsec-m2mv2-01",
# r"^/doc/html/draft-ietf-snmpsec-mibv2-00",
# r"^/doc/html/draft-ietf-snmpsec-protov2-01",
# r"^/doc/html/draft-ietf-snmpsec-tmv2-00",
# r"^/doc/html/draft-ietf-stjohns-ipso-00",
# r"^/doc/html/draft-ietf-svrloc-discovery-11",
# r"^/doc/html/draft-ietf-tcplw-extensions-00",
# r"^/doc/html/draft-ietf-tcplw-high-performance-01",
# r"^/doc/html/draft-ietf-telnet-authker-v5-01",
# r"^/doc/html/draft-ietf-telnet-compression-00",
# r"^/doc/html/draft-ietf-telnet-encryption-02",
# r"^/doc/html/draft-ietf-tewg-measure-07",
# r"^/doc/html/draft-ietf-thinosi-profile-00",
# r"^/doc/html/draft-ietf-tnfs-spec-03",
# r"^/doc/html/draft-ietf-ucp-connectivity-01",
# r"^/doc/html/draft-ietf-udlr-life-03",
# r"^/doc/html/draft-ietf-ufdl-spec-01",
# r"^/doc/html/draft-ietf-uri-roy-urn-urc-00",
# r"^/doc/html/draft-ietf-uri-urc-00",
# r"^/doc/html/draft-ietf-uri-urc-sgml-00",
# r"^/doc/html/draft-ietf-uri-urc-spec-00",
# r"^/doc/html/draft-ietf-uri-urc-trivial-00",
# r"^/doc/html/draft-ietf-uri-urn-issues-00",
# r"^/doc/html/draft-ietf-uri-urn-madsen-critique-00",
# r"^/doc/html/draft-ietf-uri-urn-res-descript-00",
# r"^/doc/html/draft-ietf-uri-urn-res-thoughts-00",
# r"^/doc/html/draft-ietf-uri-urn-syntax-00",
# r"^/doc/html/draft-ietf-uri-urn-x-dns-2-00",
# r"^/doc/html/draft-ietf-uri-urn2urc-00",
# r"^/doc/html/draft-ietf-uri-yaurn-00",
# r"^/doc/html/draft-ietf-userdoc2-fyi-biblio-00",
# r"^/doc/html/draft-ietf-uswg-fyi1-02",
# r"^/doc/html/draft-ietf-whip-reqs-summary-01",
# r"^/doc/html/draft-ietf-x400ops-admd-03",
# r"^/doc/html/draft-ietf-x400ops-dnsx400rout-02",
# r"^/doc/html/draft-ietf-x400ops-tbl-dist-00",
# r"^/doc/html/draft-ietf-x400ops-tbl-dist-part1-01",
# r"^/doc/html/draft-ietf-x400ops-tbl-dist-part2-01",
# r"^/doc/html/draft-ipsec-isakmp-mode-cfg-02",
# r"^/doc/html/draft-johnson-imhp-00",
# r"^/doc/html/draft-jseng-utf5-02",
# r"^/doc/html/draft-just-ldapv3-rescodes-03",
# r"^/doc/html/draft-karrenberg-proposal-00",
# r"^/doc/html/draft-kastenholz-loki-00",
# r"^/doc/html/draft-kempf-scope-rules-04",
# r"^/doc/html/draft-klyne-conneg-feature-match-03",
# r"^/doc/html/draft-koch-dnsind-local-compression-01",
# r"^/doc/html/draft-kzm-rap-sppi-04",
# r"^/doc/html/draft-kzm-snmpv2-adminv2-alt-00",
# r"^/doc/html/draft-kzm-snmpv2-coex-alt-00",
# r"^/doc/html/draft-kzm-snmpv2-conf-alt-00",
# r"^/doc/html/draft-kzm-snmpv2-intro-alt-00",
# r"^/doc/html/draft-kzm-snmpv2-mib-alt-00",
# r"^/doc/html/draft-kzm-snmpv2-smi-alt-00",
# r"^/doc/html/draft-kzm-snmpv2-usec-conf-alt-00",
# r"^/doc/html/draft-larson-bad-dns-res-01",
# r"^/doc/html/draft-lear-foglamps-03",
# r"^/doc/html/draft-leech-socks-protocol-v4-01",
# r"^/doc/html/draft-levi-snmp-mid-level-mgr-00",
# r"^/doc/html/draft-levi-snmp-script-language-00",
# r"^/doc/html/draft-levinson-sgml-02",
# r"^/doc/html/draft-li-bigten-addr-format-00",
# r"^/doc/html/draft-li-tap-ipv7-00",
# r"^/doc/html/draft-lloyd-ip6-iso-itu-reg-00",
# r"^/doc/html/draft-macker-mdp-framework-05",
# r"^/doc/html/draft-mahoney-snmpv2-features-00",
# r"^/doc/html/draft-mahoney-snmpv2-proto-alt-00",
# r"^/doc/html/draft-martensson-rocco-video-04",
# r"^/doc/html/draft-mccann-mobileip-sessionid-04",
# r"^/doc/html/draft-megginson-ldup-lcup-01",
# r"^/doc/html/draft-metzger-ah-sha-00",
# r"^/doc/html/draft-mpls-rsvpte-attributes-00",
# r"^/doc/html/draft-myers-imap-imsp-01",
# r"^/doc/html/draft-myers-imap-mbox-00",
# r"^/doc/html/draft-myers-smtp-mult-01",
# r"^/doc/html/draft-nelson-model-mailext-00",
# r"^/doc/html/draft-newman-imap-annotate-00",
# r"^/doc/html/draft-nguyen-bgp-ipv6-vpn-03",
# r"^/doc/html/draft-nordmark-ipv6-aaa-hooks-04",
# r"^/doc/html/draft-nyckelgard-isl-arch-04",
# r"^/doc/html/draft-ohta-address-allocation-01",
# r"^/doc/html/draft-ohta-dynamic-dns-00",
# r"^/doc/html/draft-ohta-ip-over-atm-02",
# r"^/doc/html/draft-ohta-mime-charset-names-00",
# r"^/doc/html/draft-ohta-shared-media-02",
# r"^/doc/html/draft-ohta-simple-dns-01",
# r"^/doc/html/draft-ohta-text-encoding-01",
# r"^/doc/html/draft-ohta-translation-instr-01",
# r"^/doc/html/draft-ooms-cl-multicast-03",
# r"^/doc/html/draft-ops-rfc2011-update-01",
# r"^/doc/html/draft-ouldbrahim-bgpvpn-auto-03",
# r"^/doc/html/draft-palme-autosub-06",
# r"^/doc/html/draft-pan-diffserv-mib-01",
# r"^/doc/html/draft-perkins-cnlp-support-00",
# r"^/doc/html/draft-perkins-homeaddr-dhcpopt-00",
# r"^/doc/html/draft-perkins-opaque-04",
# r"^/doc/html/draft-polk-slp-loc-auth-server-04",
# r"^/doc/html/draft-popp-cnrp-goals-01",
# r"^/doc/html/draft-pusateri-igmp-mib-00",
# r"^/doc/html/draft-pusateri-ipmulti-mib-00",
# r"^/doc/html/draft-reddy-opsawg-mud-tls-00",
# r"^/doc/html/draft-reddy-opsawg-mud-tls-03",
# r"^/doc/html/draft-reichmeyer-polterm-terminology-04",
# r"^/doc/html/draft-rekhter-arch-sipp16-addr-00",
# r"^/doc/html/draft-rekhter-bigten-addr-arch-00",
# r"^/doc/html/draft-rekhter-direct-provider-01",
# r"^/doc/html/draft-rekhter-idr-over-atm-00",
# r"^/doc/html/draft-rekhter-lsr-mobile-hosts-00",
# r"^/doc/html/draft-rekhter-select-providers-02",
# r"^/doc/html/draft-rekhter-sops-02",
# r"^/doc/html/draft-rekhter-stratum-aggregation-01",
# r"^/doc/html/draft-renwick-hippiarp-01",
# r"^/doc/html/draft-renwick-hippimib-01",
# r"^/doc/html/draft-rfced-info-corson-00",
# r"^/doc/html/draft-rfced-info-katsube-oops-00",
# r"^/doc/html/draft-rfced-info-perkins-05",
# r"^/doc/html/draft-rfced-info-pi-vs-pa-addrspac-00",
# r"^/doc/html/draft-rfced-info-senie-00",
# r"^/doc/html/draft-ronc-domain-phb-set-ldap-rep-04",
# r"^/doc/html/draft-ronc-domain-phb-set-specification-04",
# r"^/doc/html/draft-rose-limit-01",
# r"^/doc/html/draft-rose-smxp-spec-00",
# r"^/doc/html/draft-rosen-ppvpn-l2vpn-01",
# r"^/doc/html/draft-rosen-tag-stack-05",
# r"^/doc/html/draft-rosenberg-mmusic-sdp-offer-answer-01",
# r"^/doc/html/draft-rosenberg-sip-tunnels-01",
# r"^/doc/html/draft-salzr-ldap-repsig-01",
# r"^/doc/html/draft-sandick-pimsm-ssmrules-04",
# r"^/doc/html/draft-schroeppel-dnsind-ecc-04",
# r"^/doc/html/draft-simpson-exchanges-00",
# r"^/doc/html/draft-simpson-ipv6-deploy-00",
# r"^/doc/html/draft-simpson-ipv6-discovery-req-00",
# r"^/doc/html/draft-simpson-ipv6-hc-00",
# r"^/doc/html/draft-simpson-sipp-64-bit-plan-00",
# r"^/doc/html/draft-sinnreich-interdomain-sip-qos-osp-02",
# r"^/doc/html/draft-slutsman-aicd-02",
# r"^/doc/html/draft-speer-avt-layered-video-05",
# r"^/doc/html/draft-stein-green-commerce-model-00",
# r"^/doc/html/draft-svanbro-rohc-lower-layer-guidelines-04",
# r"^/doc/html/draft-templin-atn-aero-interface-00",
# r"^/doc/html/draft-templin-atn-aero-interface-21",
# r"^/doc/html/draft-teraoka-ipv6-mobility-sup-07",
# r"^/doc/html/draft-thayer-seccomp-04",
# r"^/doc/html/draft-traina-bgp-confed-00",
# r"^/doc/html/draft-treese-class-desc-00",
# r"^/doc/html/draft-vaudreuil-binaryheaders-01",
# r"^/doc/html/draft-vaudreuil-enum-e164dir-05",
# r"^/doc/html/draft-veizades-ipng-svrloc-00",
# r"^/doc/html/draft-villamizar-isis-omp-01",
# r"^/doc/html/draft-waldbusser-conventions-01",
# r"^/doc/html/draft-waldbusser-rmonmib-apm-04",
# r"^/doc/html/draft-waldbusser-ssecimpl-01",
# r"^/doc/html/draft-waters-snmpv1-sec-mech-00",
# r"^/doc/html/draft-weider-comindex-00",
# r"^/doc/html/draft-wijnen-snmpv2-snmpv2t-00",
# r"^/doc/html/draft-woundy-dhcpleasequery-04",
# r"^/doc/html/draft-wright-policy-mpls-04",
# r"^/doc/html/draft-yu-asn1-pitfalls-04",
# r"^/doc/html/draft-yu-rpd-00",
# r"^/doc/html/draft-zaccone-nat-rsip-gen-arch-02",
# r"^/doc/html/draft-zaccone-nat-transp-fram-02",
# r"^/doc/html/draft-zaccone-nat-transport-03",
# r"^/doc/html/status-change-icmpv6-dns-ipv6-to-internet-standard",
r"^/doc/html/draft-[0-9ac-z]",
r"^/doc/html/draft-b[0-9b-z]",
r"^/doc/pdf/draft-[0-9ac-z]",
r"^/doc/pdf/draft-b[0-9b-z]",
r"^/doc/html/charter-.*",
r"^/doc/html/status-.*",
r"^/doc/html/rfc.*",
r"^/static/coverage/",
r"^/meeting/6[0-4]/agenda",
r"^https?://www.ietf.org/",
r"^/meeting/\d{,2}/agenda", # no agendas < 100
):
if re.search(pattern, url):
return True
@ -691,8 +290,16 @@ logfile = None
if args.logfile:
logfile = open(args.logfile, "w")
vnu = None
# --- Main ---
def do_exit(code):
if vnu:
vnu.terminate()
sys.exit(code)
if __name__ == "__main__":
if (args.user):
# log in as user, to have the respective HTML generated by the templates
@ -702,7 +309,7 @@ if __name__ == "__main__":
if (response.status_code != 200):
log("Could not log in as %s, HTML response %d" %
(args.user, response.status_code))
sys.exit(1)
do_exit(1)
# Run django system checks and checks from ietf.checks:
error_list = django.core.checks.run_checks()
@ -718,10 +325,13 @@ if __name__ == "__main__":
for entry in error_list:
print(entry)
if args.validator_nu:
vnu = start_vnu_server(port=8887)
while urls:
if args.random:
# popitem() is documented to be random, but really isn't
url = random.choice(urls.keys())
url = random.choice(list(urls.keys()))
referrer = urls.pop(url)
else:
url, referrer = urls.popitem()
@ -746,10 +356,10 @@ if __name__ == "__main__":
elapsed = datetime.datetime.now() - request_start
except KeyboardInterrupt:
log(" ... was fetching %s" % url)
sys.exit(1)
do_exit(1)
except:
elapsed = datetime.datetime.now() - request_start
tags = [ u"FAIL (from [ %s ])" % (",\n\t".join(get_referrers(url))) ]
tags = [ "FAIL (from [ %s ])" % (",\n\t".join(get_referrers(url))) ]
log("%2d:%02d:%02d %7d %6d %s %6.3fs %s %s" % (hrs,min,sec, len(visited), len(urls), 500, elapsed.total_seconds(), url, " ".join(tags)))
log("=============")
log(traceback.format_exc())
@ -760,7 +370,7 @@ if __name__ == "__main__":
if r.status_code in (301, 302):
u = strip_url(r["Location"])
if u not in visited and u not in urls:
if not url.startswith("/") and u not in visited and u not in urls:
urls[u] = referrer # referrer is original referrer, not redirected url
referrers[u] = referrer
@ -799,8 +409,9 @@ if __name__ == "__main__":
log("=============")
else:
tags.append(u"FAIL (from %s)" % (referrer, ))
errors += 1
tags.append("FAIL (from {})".format(referrer))
if not url.startswith("/person/"): # FIXME: those fail sometimes
errors += 1
if elapsed.total_seconds() > slow_threshold:
tags.append("SLOW")
@ -816,7 +427,7 @@ if __name__ == "__main__":
log("%2d:%02d:%02d %7d %6d %s %6.3fs %s %s" % (hrs,min,sec, len(visited), len(urls), r.status_code, elapsed.total_seconds(), url, " ".join(tags)))
if ((errors or warnings) and args.pedantic):
sys.exit(1)
do_exit(1)
if logfile:
logfile.close()
@ -824,7 +435,7 @@ if __name__ == "__main__":
if errors > 0:
sys.stderr.write("Found %s errors, grep output for FAIL for details\n" % errors)
sys.exit(1)
do_exit(1)
else:
sys.stderr.write("Found no errors.\n")
if warnings > 0:

Binary file not shown.

View file

@ -23,6 +23,7 @@ RUN apt-get install -qy \
bash \
build-essential \
curl \
default-jdk \
docker-ce-cli \
enscript \
gawk \

View file

@ -32,6 +32,7 @@ RUN apt-get install -qy \
bash \
build-essential \
curl \
default-jdk \
docker-ce-cli \
enscript \
fish \

View file

@ -4,6 +4,7 @@
import datetime
import re
import os
from urllib.parse import urljoin
from email.utils import parseaddr
@ -17,10 +18,13 @@ from django.utils.html import strip_tags
from django.utils.encoding import force_text
from django.utils.encoding import force_str # pyflakes:ignore force_str is used in the doctests
from django.urls import reverse as urlreverse
from django.core.cache import cache
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
import debug # pyflakes:ignore
from ietf.doc.models import BallotDocEvent
from ietf.doc.models import BallotDocEvent, DocAlias
from ietf.doc.models import ConsensusDocEvent
from ietf.utils.html import sanitize_fragment
from ietf.utils import log
@ -184,49 +188,113 @@ def rfceditor_info_url(rfcnum : str):
"""Link to the RFC editor info page for an RFC"""
return urljoin(settings.RFC_EDITOR_INFO_BASE_URL, f'rfc{rfcnum}')
def doc_exists(name):
"""Check whether a given document exists"""
def find_unique(n):
key = hash(n)
found = cache.get(key)
if not found:
exact = DocAlias.objects.filter(name=n).first()
found = exact.name if exact else "_"
cache.set(key, found)
return None if found == "_" else found
# all documents exist when tests are running
if settings.SERVER_MODE == 'test':
# unless we are running test-crawl, which would otherwise 404
if "DJANGO_URLIZE_IETF_DOCS_PRODUCTION" not in os.environ:
return True
# chop away extension
extension_split = re.search(r"^(.+)\.(txt|ps|pdf)$", name)
if extension_split:
name = extension_split.group(1)
if find_unique(name):
return True
# check for embedded rev - this may be ambiguous, so don't
# chop it off if we don't find a match
rev_split = re.search("^(.+)-([0-9]{2,})$", name)
if rev_split:
name = rev_split.group(1)
if find_unique(name):
return True
return False
def link_charter_doc_match1(match):
if not doc_exists(match[0]):
return match[0]
return f'<a href="/doc/{match[1][:-1]}/{match[2]}/">{match[0]}</a>'
def link_charter_doc_match2(match):
if not doc_exists(match[0]):
return match[0]
return f'<a href="/doc/{match[1][:-1]}/{match[2]}/">{match[0]}</a>'
def link_non_charter_doc_match(match):
if len(match[3])==2 and match[3].isdigit():
if not doc_exists(match[0]):
return match[0]
if len(match[3]) == 2 and match[3].isdigit():
return f'<a href="/doc/{match[2][:-1]}/{match[3]}/">{match[0]}</a>'
else:
return f'<a href="/doc/{match[2]}{match[3]}/">{match[0]}</a>'
@register.filter(name='urlize_ietf_docs', is_safe=True, needs_autoescape=True)
def link_other_doc_match(match):
# there may be whitespace in the match
doc = re.sub(r"\s+", "", match[0])
if not doc_exists(doc):
return match[0]
return f'<a href="/doc/{match[2].strip().lower()}{match[3]}/">{match[1]}</a>'
@register.filter(name="urlize_ietf_docs", is_safe=True, needs_autoescape=True)
def urlize_ietf_docs(string, autoescape=None):
"""
Make occurrences of RFC NNNN and draft-foo-bar links to /doc/.
"""
if autoescape and not isinstance(string, SafeData):
string = escape(string)
exp1 = r"\b(charter-(?:[\d\w\.+]+-)*)(\d\d-\d\d)(\.txt)?\b"
exp2 = r"\b(charter-(?:[\d\w\.+]+-)*)(\d\d)(\.txt)?\b"
if "<" in string:
string = escape(string)
else:
string = mark_safe(string)
exp1 = r"\b(?<![/\-:=#])(charter-(?:[\d\w\.+]+-)*)(\d\d-\d\d)(\.txt)?\b"
exp2 = r"\b(?<![/\-:=#])(charter-(?:[\d\w\.+]+-)*)(\d\d)(\.txt)?\b"
if re.search(exp1, string):
string = re.sub(
exp1,
lambda x: f'<a href="/doc/{x[1][:-1]}/{x[2]}/">{x[0]}</a>',
link_charter_doc_match1,
string,
flags=re.IGNORECASE | re.ASCII,
)
elif re.search(exp2, string):
elif re.search(exp2, string):
string = re.sub(
exp2,
lambda x: f'<a href="/doc/{x[1][:-1]}/{x[2]}/">{x[0]}</a>',
link_charter_doc_match2,
string,
flags=re.IGNORECASE | re.ASCII,
)
string = re.sub(
r"\b(?<![/-])(((?:draft-|bofreq-|conflict-review-|status-change-)(?:[\d\w\.+]+-)*)([\d\w\.+]+?)(\.txt)?)\b",
r"\b(?<![/\-:=#])(((?:draft-|bofreq-|conflict-review-|status-change-)(?:[\d\w\.+]+-)*)([\d\w\.+]+?)(\.txt)?)\b(?![-@])",
link_non_charter_doc_match,
string,
flags=re.IGNORECASE | re.ASCII,
)
string = re.sub(
# r"\b((RFC|BCP|STD|FYI|(?:draft-|bofreq-|conflict-review-|status-change-|charter-)[-\d\w.+]+)\s*0*(\d+))\b",
r"\b(?<!-)((RFC|BCP|STD|FYI)\s*0*(\d+))\b",
lambda x: f'<a href="/doc/{x[2].strip().lower()}{x[3]}/">{x[1]}</a>',
r"\b(?<![/\-:=#])((RFC|BCP|STD|FYI)\s*0*(\d+))\b",
link_other_doc_match,
string,
flags=re.IGNORECASE | re.ASCII,
)
return mark_safe(string)
urlize_ietf_docs = stringfilter(urlize_ietf_docs)
@register.filter(name='urlize_related_source_list', is_safe=True, needs_autoescape=True)
@ -444,7 +512,7 @@ def format_snippet(text, trunc_words=25):
@register.simple_tag
def doc_edit_button(url_name, *args, **kwargs):
"""Given URL name/args/kwargs, looks up the URL just like "url" tag and returns a properly formatted button for the document material tables."""
return mark_safe('<a class="btn btn-primary btn-sm" type="button" href="%s">Edit</a>' % (urlreverse(url_name, args=args, kwargs=kwargs)))
return mark_safe('<a class="btn btn-primary btn-sm" href="%s">Edit</a>' % (urlreverse(url_name, args=args, kwargs=kwargs)))
@register.filter
def textify(text):
@ -765,3 +833,16 @@ def absurl(viewname, **kwargs):
Uses settings.IDTRACKER_BASE_URL as the base.
"""
return urljoin(settings.IDTRACKER_BASE_URL, urlreverse(viewname, kwargs=kwargs))
@register.filter
def is_valid_url(url):
"""
Check if the given URL is syntactically valid
"""
validate_url = URLValidator()
try:
validate_url(url)
except ValidationError:
return False
return True

View file

@ -46,14 +46,22 @@ class IetfFiltersTests(TestCase):
),
(
"draft-madanapalli-nd-over-802.16-problems",
'<a href="/doc/draft-madanapalli-nd-over-802.16-problems/">draft-madanapalli-nd-over-802.16-problems</a>'
'<a href="/doc/draft-madanapalli-nd-over-802.16-problems/">draft-madanapalli-nd-over-802.16-problems</a>'
),
(
"draft-madanapalli-nd-over-802.16-problems-02.txt",
'<a href="/doc/draft-madanapalli-nd-over-802.16-problems/02/">draft-madanapalli-nd-over-802.16-problems-02.txt</a>'
'<a href="/doc/draft-madanapalli-nd-over-802.16-problems/02/">draft-madanapalli-nd-over-802.16-problems-02.txt</a>'
),
(
'<a href="mailto:draft-ietf-some-names@ietf.org">draft-ietf-some-names@ietf.org</a>',
'<a href="mailto:draft-ietf-some-names@ietf.org">draft-ietf-some-names@ietf.org</a>',
),
(
"http://ieee802.org/1/files/public/docs2015/cn-thaler-Qcn-draft-PAR.pdf",
"http://ieee802.org/1/files/public/docs2015/cn-thaler-Qcn-draft-PAR.pdf"
)
]
# Some edge cases scraped from existing old draft names
for name in [
# "draft-odell-8+8", # This fails since + matches the right side of \b

View file

@ -23,6 +23,7 @@ from ietf.person.models import Email
from ietf.review.utils import can_manage_review_requests_for_team
from ietf.utils import log
from ietf.utils.history import get_history_object_for, copy_many_to_many_for_history
from ietf.doc.templatetags.ietf_filters import is_valid_url
from functools import reduce
def save_group_in_history(group):
@ -208,7 +209,8 @@ def construct_group_menu_context(request, group, selected, group_type, others):
entries.append(("Photos", urlreverse("ietf.group.views.group_photos", kwargs=kwargs)))
entries.append(("Email expansions", urlreverse("ietf.group.views.email", kwargs=kwargs)))
if group.list_archive.startswith("http:") or group.list_archive.startswith("https:") or group.list_archive.startswith("ftp:"):
entries.append((mark_safe("List archive &raquo;"), group.list_archive))
if is_valid_url(group.list_archive):
entries.append((mark_safe("List archive &raquo;"), group.list_archive))
# actions

View file

@ -35,7 +35,7 @@ from ietf.utils.validators import ( validate_file_size, validate_mime_type,
# need to insert empty option for use in ChoiceField
# countries.insert(0, ('', '-'*9 ))
countries.insert(0, ('', ''))
countries.insert(0, ('', '-' * 9))
timezones.insert(0, ('', '-' * 9))
# -------------------------------------------------
@ -827,4 +827,4 @@ def sessiondetailsformset_factory(min_num=1, max_num=3):
min_num=min_num,
max_num=max_num,
extra=max_num, # only creates up to max_num total
)
)

View file

@ -22,7 +22,9 @@ VALID_BLUESHEET_EXTENSIONS = ('.pdf','.jpg','.jpeg')
class RecordingForm(forms.Form):
external_url = forms.URLField(label='Url')
session = forms.ModelChoiceField(queryset=Session.objects,empty_label='')
session = forms.ModelChoiceField(queryset=Session.objects)
session.widget.attrs['class'] = "select2-field"
session.widget.attrs['data-minimum-input-length'] = 0
def __init__(self, *args, **kwargs):
self.meeting = kwargs.pop('meeting')

View file

@ -1,6 +0,0 @@
/* proceedings-recordings.js - utility functions */
$(document).ready(function() {
$('#id_session').select2({ placeholder: 'Type group acronym or part of session name', width: '450px' });;
});

View file

@ -11,7 +11,6 @@
{% block extrahead %}{{ block.super }}
<script src="{% static 'ietf/js/jquery-ui.js' %}"></script>
<script src="{% static 'ietf/js/select2.js' %}"></script>
<script src="{% static 'secr/js/proceedings-recording.js' %}"></script>
{% endblock %}
{% block breadcrumbs %}{{ block.super }}
@ -119,4 +118,4 @@
{% block footer-extras %}
{% include "includes/upload_footer.html" %}
{% endblock %}
{% endblock %}

View file

@ -25,7 +25,7 @@
.utc();
item.end_ts = moment.unix(this.getAttribute("data-end-time"))
.utc();
if (this.hasAttribute("weekday")) {
if (this.hasAttribute("data-weekday")) {
item.format = 2;
} else {
item.format = 1;

View file

@ -32,8 +32,8 @@
<input id="subject" class="form-control" type="text" placeholder="{{ subject }}" disabled>
</div>
<div class="mb-3">
<label class="form-label" for="body">Body</label>
<pre id="body" class="border p-3">{{ body|maybewordwrap }}</pre>
<p class="form-label">Body</p>
<pre class="border p-3">{{ body|maybewordwrap }}</pre>
</div>
<button type="submit" class="btn btn-danger">Send</button>
<a class="btn btn-secondary float-end"
@ -41,4 +41,4 @@
Back
</a>
</form>
{% endblock %}
{% endblock %}

View file

@ -122,7 +122,7 @@
</div>
</div>
<div class="card-body">
<pre class="ballot pasted">{{ p.discuss|linkify|urlize_ietf_docs }}</pre>
<pre class="ballot pasted">{{ p.discuss|urlize_ietf_docs|linkify }}</pre>
</div>
</div>
{% endif %}
@ -148,7 +148,7 @@
</div>
</div>
<div class="card-body">
<pre class="ballot pasted">{{ p.comment|linkify|urlize_ietf_docs }}</pre>
<pre class="ballot pasted">{{ p.comment|urlize_ietf_docs|linkify }}</pre>
</div>
</div>
{% endif %}
@ -199,11 +199,11 @@
</div>
{% if p.pos.blocking and p.discuss %}
<div class="card-body">
<pre class="ballot pasted">{{ p.discuss|linkify|urlize_ietf_docs }}</pre>
<pre class="ballot pasted">{{ p.discuss|urlize_ietf_docs|linkify }}</pre>
</div>
{% else %}
<div class="card-body">
<pre class="ballot pasted">{{ p.comment|linkify|urlize_ietf_docs }}</pre>
<pre class="ballot pasted">{{ p.comment|urlize_ietf_docs|linkify }}</pre>
</div>
{% endif %}
</div>

View file

@ -107,13 +107,13 @@
{% if resources %}
{% for resource in resources|dictsort:"display_name" %}
{% if resource.name.type.slug == 'url' or resource.name.type.slug == 'email' %}
<a href="{{ resource.value }}" title="{{ resource.name.name }}">
<a href="{{ resource.value|urlencode }}" title="{{ resource.name.name }}">
{% firstof resource.display_name resource.name.name %}
</a>
<br>
{# Maybe make how a resource displays itself a method on the class so templates aren't doing this switching #}
{% else %}
<span title="{{ resource.name.name }}">{% firstof resource.display_name resource.name.name %}: {{ resource.value }}</span>
<span title="{{ resource.name.name }}">{% firstof resource.display_name resource.name.name %}: {{ resource.value|escape }}</span>
<br>
{% endif %}
{% endfor %}
@ -164,4 +164,4 @@
</script>
<script src="{% static 'ietf/js/document_timeline.js' %}">
</script>
{% endblock %}
{% endblock %}

View file

@ -230,7 +230,7 @@
{{ doc.canonical_name }}-{{ doc.rev }}
</div>
<div class="card-body">
<pre>{{ content|maybewordwrap|linkify|urlize_ietf_docs }}</pre>
<pre>{{ content|maybewordwrap|urlize_ietf_docs|linkify }}</pre>
</div>
</div>
{% endif %}

View file

@ -139,7 +139,7 @@
{{ doc.name }}-{{ doc.rev }}
</div>
<div class="card-body">
<pre>{{ content|maybewordwrap|linkify|urlize_ietf_docs }}</pre>
<pre>{{ content|maybewordwrap|urlize_ietf_docs|linkify }}</pre>
</div>
</div>
{% endif %}

View file

@ -366,23 +366,30 @@
{% if resources or doc.group and doc.group.list_archive %}
{% for resource in resources|dictsort:"display_name" %}
{% if resource.name.type.slug == 'url' or resource.name.type.slug == 'email' %}
<a href="{{ resource.value }}" title="{{ resource.name.name }}">
<a href="{{ resource.value|urlencode }}" title="{{ resource.name.name }}">
{% firstof resource.display_name resource.name.name %}
</a>
<br>
{# Maybe make how a resource displays itself a method on the class so templates aren't doing this switching #}
{% else %}
<span title="{{ resource.name.name }}">
{% firstof resource.display_name resource.name.name %}: {{ resource.value }}
{% firstof resource.display_name resource.name.name %}: {{ resource.value|escape }}
</span>
<br>
{% endif %}
{% endfor %}
{% if doc.group and doc.group.list_archive %}
<a href="{{ doc.group.list_archive }}?q={{ doc.name }}">
Mailing list discussion
</a>
<br>
{% if doc.group.list_archive|startswith:settings.MAILING_LIST_ARCHIVE_URL %}
<a href="{{ doc.group.list_archive }}?q={{ doc.name }}">
Mailing list discussion
</a>
{% elif doc.group.list_archive|is_valid_url %}
<a href="{{ doc.group.list_archive }}">
Mailing list discussion
</a>
{% else %}
{{ doc.group.list_archive|urlencode }}
{% endif %}
{% endif %}
{% endif %}
</td>

View file

@ -3,7 +3,7 @@
{% load origin %}
{% load static %}
{% load ietf_filters textfilters %}
{% block title %}{{ doc.title }}{% endblock %}
{% block title %}{{ doc.title|default:"Untitled" }}{% endblock %}
{% block content %}
{% origin %}
{{ top|safe }}
@ -36,7 +36,7 @@
{% doc_edit_button 'ietf.doc.views_material.edit_material' name=doc.name action="title" %}
{% endif %}
</td>
<th scope="row">{{ doc.title }}</th>
<th scope="row">{{ doc.title|default:'<span class="text-muted">(None)</span>' }}</th>
</tr>
{% if doc.abstract or doc.type_id == 'slides' and can_manage_material and not snapshot %}
<tr>
@ -122,7 +122,7 @@
{% if content_is_html %}
{{ content|sanitize|safe }}
{% else %}
<pre>{{ content|maybewordwrap|linkify|urlize_ietf_docs }}</pre>
<pre>{{ content|maybewordwrap|urlize_ietf_docs|linkify }}</pre>
{% endif %}
{% else %}
Not available as plain text.
@ -136,4 +136,4 @@
{% block js %}
<script src="{% static 'ietf/js/d3.js' %}"></script>
<script src="{% static 'ietf/js/document_timeline.js' %}"></script>
{% endblock %}
{% endblock %}

View file

@ -47,7 +47,7 @@
<div class="card mt-5">
<div class="card-header">{{ doc.name }}-{{ doc.rev }}</div>
<div class="card-body">
<pre class="pasted">{{ content|maybewordwrap|linkify|urlize_ietf_docs }}</pre>
<pre class="pasted">{{ content|maybewordwrap|urlize_ietf_docs|linkify }}</pre>
</div>
</div>
{% endif %}

View file

@ -162,7 +162,7 @@
{{ doc.name }}-{{ doc.rev }}
</div>
<div class="card-body">
<pre>{{ content|maybewordwrap|linkify|urlize_ietf_docs }}</pre>
<pre>{{ content|maybewordwrap|urlize_ietf_docs|linkify }}</pre>
</div>
</div>
{% endif %}

View file

@ -3,7 +3,7 @@
{% origin %}
{% load ietf_filters %}
<h1>
{{ doc.title }}
{{ doc.title|default:"(Untitled)" }}
<br>
<small class="text-muted">{{ name }}</small>
</h1>
@ -16,4 +16,4 @@
</a>
</li>
{% endfor %}
</ul>
</ul>

View file

@ -17,7 +17,7 @@
</h2>
{% for name, text, url in writeups %}
{% if name %}<h3 class="mt-5">{{ name }}</h3>{% endif %}
{% if text %}<pre class="border p-3 my-3">{{ text|linkify|urlize_ietf_docs }}</pre>{% endif %}
{% if text %}<pre class="border p-3 my-3">{{ text|urlize_ietf_docs|linkify }}</pre>{% endif %}
{% if can_edit %}
<p>
<a href="{{ url }}" class="btn btn-primary">
@ -32,4 +32,4 @@
{% endif %}
{% endfor %}
{% endfor %}
{% endblock %}
{% endblock %}

View file

@ -18,7 +18,7 @@
<thead>
<tr>
<th scope="col" data-sort="ref">Referenced RFC</th>
<th scope="col" data-sort="id">Internet-Draft making the reference</th>
<th scope="col" data-sort="refby">Internet-Draft making the reference</th>
</tr>
</thead>
<tbody>
@ -41,4 +41,4 @@
{% endblock %}
{% block js %}
<script src="{% static "ietf/js/list.js" %}"></script>
{% endblock %}
{% endblock %}

View file

@ -60,7 +60,7 @@
{% endif %}
{% if doc.note %}
<br>
<i>Note: {{ doc.note|linkify|urlize_ietf_docs|linebreaksbr }}</i>
<i>Note: {{ doc.note|urlize_ietf_docs|linkify|linebreaksbr }}</i>
{% endif %}
</td>
<td>

View file

@ -19,7 +19,7 @@
</tr>
<tr>
<td></td>
<th scope="row">Requested rev.</th>
<th scope="row">Requested revision</th>
<td>
{% if review_req.requested_rev %}
{{ review_req.requested_rev }}
@ -299,12 +299,10 @@
<th scope="row">
</th>
<th scope="row">
Reviewed rev.
Reviewed revision
</th>
<td>
<a href="{% url "ietf.doc.views_doc.document_main" name=review_req.doc.name rev=assignment.reviewed_rev %}">
{{ assignment.reviewed_rev }}
</a>
<a href="{% url "ietf.doc.views_doc.document_main" name=review_req.doc.name rev=assignment.reviewed_rev %}">{{ assignment.reviewed_rev }}</a>
{% if assignment.reviewed_rev != review_req.doc.rev %}(document currently at {{ review_req.doc.rev }}){% endif %}
</td>
</tr>
@ -314,7 +312,7 @@
<th scope="row">
</th>
<th scope="row">
Review result
Result
</th>
<td class="{% if assignment.result.name|slice:5 == 'Ready' %}text-success{% else %}text-danger{% endif %}">
{{ assignment.result.name }}
@ -326,7 +324,7 @@
<th scope="row">
</th>
<th scope="row">
Review completed:
Completed
</th>
<td>
{{ assignment.completed_on|date:"Y-m-d" }}
@ -343,4 +341,4 @@
</i>
Assign reviewer
</a>
{% endif %}
{% endif %}

View file

@ -11,11 +11,11 @@
<br>
<small class="text-muted">{{ doc.canonical_name }}-{{ doc.rev }}</small>
</h1>
<pre class="border p-3 my-3">{{writeup|linkify|urlize_ietf_docs|wordwrap:"80"}}</pre>
<pre class="border p-3 my-3">{{writeup|maybewordwrap|urlize_ietf_docs|linkify}}</pre>
{% if can_edit %}
<a class="btn btn-primary"
href="{% url 'ietf.doc.views_draft.edit_shepherd_writeup' name=doc.name %}">Edit</a>
{% endif %}
<a class="btn btn-secondary float-end"
href="{% url "ietf.doc.views_doc.document_main" name=doc.name %}">Back</a>
{% endblock %}
{% endblock %}

View file

@ -50,7 +50,7 @@
</h2>
{% if area.description %}
<p>
{{ area.description|linkify|urlize_ietf_docs|safe }}
{{ area.description|urlize_ietf_docs|linkify|safe }}
</p>
{% endif %}
{% endfor %}

View file

@ -22,7 +22,7 @@
{{ rpt.time|date:"Y-m-d" }}
</div>
<div class="card-body">
<pre class="pasted">{{ rpt.desc|default:"(none)"|linkify|urlize_ietf_docs }}</pre>
<pre class="pasted">{{ rpt.desc|default:"(none)"|urlize_ietf_docs|linkify }}</pre>
</div>
</div>
{% endfor %}
@ -38,7 +38,7 @@
{{ rpt.time|date:"Y-m-d" }}
</div>
<div class="card-body">
<pre class="pasted">{{ rpt.desc|default:"(none)"|linkify|urlize_ietf_docs }}</pre>
<pre class="pasted">{{ rpt.desc|default:"(none)"|urlize_ietf_docs|linkify }}</pre>
</div>
</div>
{% endfor %}

View file

@ -148,10 +148,10 @@
{% for resource in resources|dictsort:"display_name" %}
{# Maybe make how a resource displays itself a method on the class so templates aren't doing this switching #}
{% if resource.name.type.slug == 'url' or resource.name.type.slug == 'email' %}
<a href="{{ resource.value }}" title="{{ resource.name.name }}">
<a href="{{ resource.value|urlencode }}" title="{{ resource.name.name }}">
{% firstof resource.display_name resource.name.name %}</a>{% else %}
<span title="{{ resource.name.name }}">
{% firstof resource.display_name resource.name.name %}: {{ resource.value }}
{% firstof resource.display_name resource.name.name %}: {{ resource.value|escape }}
</span>{% endif %}{% if not forloop.last %}{% if resource.display_name|length < 15 and resource.name.name|length < 15 and resources|length <= 3 %},{% else %}<br>{% endif %}{% endif %}
{% endfor %}
{% endif %}
@ -309,7 +309,7 @@
{{ group.type.desc.title }}
</h2>
{# the linebreaks filter adds <p>, no surrounding <p> necessary: #}
{{ group.charter_text|linkify|urlize_ietf_docs|linebreaks }}
{{ group.charter_text|urlize_ietf_docs|linkify|linebreaks }}
{% else %}
<h2 class="mt-3">
{% if requested_close or group.state_id == "conclude" %}Final{% endif %}
@ -333,4 +333,4 @@
</p>
{% endif %}
{% endif %}
{% endblock %}
{% endblock %}

View file

@ -28,7 +28,7 @@
</p>
</div>
{% endif %}
<pre class="border p-3 my-3 pasted">{{ status_update.desc|default:"(none)"|linkify|urlize_ietf_docs }}</pre>
<pre class="border p-3 my-3 pasted">{{ status_update.desc|default:"(none)"|urlize_ietf_docs|linkify }}</pre>
{% if can_provide_status_update %}
<a id="edit_button"
class="btn btn-primary"

View file

@ -9,7 +9,7 @@
{% block content %}
{% origin %}
<h1>Status update for {{ group.type.name }} {{ group.acronym }} at {{ meeting }}</h1>
<pre class="border p-3 my-3 pasted">{{ status_update.desc|default:"(none)"|linkify|urlize_ietf_docs }}</pre>
<pre class="border p-3 my-3 pasted">{{ status_update.desc|default:"(none)"|urlize_ietf_docs|linkify }}</pre>
<a class="btn btn-secondary float-end"
href="{% url "ietf.meeting.views.proceedings" num=meeting.number %}">Back</a>
{% endblock %}

View file

@ -9,12 +9,12 @@
{% block content %}
{% origin %}
<h1>
{{ group.name }} ({{ group.acronym }})
{% if group.state_id == "dormant" or group.state_id == "conclude" %}
<span class="badge bg-warning float-end">Concluded {{ group.type.name }}</span>
<span class="badge bg-warning float-end ms-3">Concluded {{ group.type.name }}</span>
{% endif %}
{% if group.state_id == "replaced" %}<span class="badge bg-warning float-end">Replaced {{ group.type.name }}</span>{% endif %}
{% if group.state_id == "proposed" %}<span class="badge bg-info float-end">Proposed {{ group.type.name }}</span>{% endif %}
{% if group.state_id == "replaced" %}<span class="badge bg-warning float-end ms-3">Replaced {{ group.type.name }}</span>{% endif %}
{% if group.state_id == "proposed" %}<span class="badge bg-info float-end ms-3">Proposed {{ group.type.name }}</span>{% endif %}
{{ group.name }} ({{ group.acronym }})
</h1>
<ul class="nav nav-tabs my-3">
{% for name, url in menu_entries %}
@ -40,4 +40,4 @@
{% endblock %}
{% block js %}
<script src="{% static "ietf/js/list.js" %}"></script>
{% endblock %}
{% endblock %}

View file

@ -51,7 +51,7 @@
<div class="row request-metadata">
<div class="col-sm-6">
<p class="lead">
{{ r.doc.title|linkify|urlize_ietf_docs }}
{{ r.doc.title|urlize_ietf_docs|linkify }}
</p>
{% if r.pk != None %}
<span class="fw-bold">Requested:</span>
@ -143,7 +143,7 @@
</div>
<div class="col-sm-6">
<span class="fw-bold">Abstract:</span>
{{ r.doc.abstract|linkify|urlize_ietf_docs }}
{{ r.doc.abstract|urlize_ietf_docs|linkify }}
</div>
</div>
{% if r.form.non_field_errors %}

View file

@ -30,7 +30,7 @@
</thead>
<tbody>
<tr class="table-info" id="unassigned-review-requests">
<th scope="col" colspan="7">Unassigned review requests</th>
<th scope="col" colspan="6">Unassigned review requests</th>
</tr>
</tbody>
<tbody>
@ -149,7 +149,7 @@
</thead>
<tbody>
<tr class="table-info">
<th scope="col" colspan="8">
<th scope="col" colspan="6">
Closed review requests
</th>
</tr>

View file

@ -1,7 +1,7 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load ietf_filters %}
{% load ietf_filters textfilters %}
{% block pagehead %}<link rel="alternate" type="application/atom+xml" href="/feed/iesg-agenda/">{% endblock %}
{% block title %}IESG agenda: {{ date }}{% endblock %}
{% block content %}
@ -107,8 +107,8 @@
{% endif %}
{% endif %}
{% if num|startswith:"6." and user|has_role:"Area Director,IAB Chair,Secretariat" %}
<pre class="border p-3">{{ section.text|wordwrap:"80" }}</pre>
<pre class="border p-3">{{ section.text|maybewordwrap|urlize_ietf_docs|linkify }}</pre>
{% endif %}
{% endfor %}
{% endblock %}
{% block js %}<script>// automatically generate a right-hand navigation tab for long pages</script>{% endblock %}
{% block js %}<script>// automatically generate a right-hand navigation tab for long pages</script>{% endblock %}

View file

@ -15,6 +15,7 @@
<small class="text-muted">{{ user.username }}</small>
</h1>
{% csrf_token %}
{% if person.apikeys.all %}
<table class="table table-sm tablesorter">
<thead>
<tr >
@ -49,13 +50,12 @@
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td>You have no personal API keys.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>You have no personal API keys.</p>
{% endif %}
<a href="{% url 'ietf.ietfauth.views.apikey_create' %}"
class="btn btn-primary add-apikey my-3">
Get a new personal API key
@ -63,4 +63,4 @@
{% endblock %}
{% block js %}
<script src="{% static "ietf/js/list.js" %}"></script>
{% endblock %}
{% endblock %}

View file

@ -52,7 +52,8 @@
<td>
{% if e.message %}
{% if e.response_due %}<span class="badge bg-info">Response due {{ e.response_due|date:"Y-m-d" }}</span>{% endif %}
<pre>{{ e.message|render_message_for_history|format_history_text:"100" }}</pre>
{# FIXME: can't do format_history_text, because that inserts a <div> into the <pre>, which is illegal. Need to rework the snippeting. #}
<pre>{{ e.message|render_message_for_history|urlize_ietf_docs|linkify }}</pre>
{% else %}
{{ e.desc|format_history_text }}
{% endif %}
@ -65,4 +66,4 @@
{% endblock %}
{% block js %}
<script src="{% static "ietf/js/list.js" %}"></script>
{% endblock %}
{% endblock %}

View file

@ -201,7 +201,7 @@
Body
</th>
<td>
<pre>{{ liaison.body|maybewordwrap:"80"|linkify|urlize_ietf_docs }}</pre>
<pre>{{ liaison.body|maybewordwrap:"80"|urlize_ietf_docs|linkify }}</pre>
</td>
</tr>
{% endif %}

View file

@ -52,7 +52,14 @@
{% for fieldset in form.fieldsets %}
<h2>{{ fieldset.name }}</h2>
{% for field in fieldset %}
{% bootstrap_field field layout="horizontal" %}
{% if field.id_for_label != "id_attachments" %}
{% bootstrap_field field layout="horizontal" %}
{% else %}
<div class="row mb-3">
<p class="col-md-2 fw-bold col-form-label">{{ field.label }}</p>
<div class="col-md-10">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
{% endfor %}
<a class="btn btn-danger float-end"
@ -70,4 +77,4 @@
{% block js %}
{{ form.media.js }}
<script src="{% static 'ietf/js/liaisons.js' %}"></script>
{% endblock %}
{% endblock %}

View file

@ -57,7 +57,7 @@
{{ schedule.meeting.agenda_info_note|removetags:"h1"|safe }}
</p>
{% endif %}
{% include 'meeting/tz-display.html' with meeting_timezone=timezone only %}
{% include 'meeting/tz-display.html' with id_suffix="" meeting_timezone=timezone only %}
{% include "meeting/agenda_filter.html" with filter_categories=filter_categories customize_button_text="Filter this agenda view..." always_show=personalize %}
{% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting personalize=personalize only %}
<div class="input-group mb-3">
@ -84,7 +84,7 @@
{% endfor %}
</ul>
<a class="btn btn-outline-primary {% if non_area_keywords|length == 0 %}disabled{% endif %}"
href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}?show={{ non_area_keywords|join:',' }}">
href="{% if non_area_keywords %}{% url 'ietf.meeting.views.agenda_ical' num=schedule.meeting.number %}?show={{ non_area_keywords|join:',' }}{% else %}#{% endif %}">
Download non-area events
</a>
</div>
@ -181,18 +181,20 @@
{% if item.session.current_status == 'canceled' %}
<span class="badge bg-danger float-end">CANCELLED</span>
{% else %}
<div class="float-end ps-2">
{% if item.slot_type.slug == 'other' %}
{% if item.session.agenda or item.session.remote_instructions or item.session.agenda_note %}
{% include "meeting/session_buttons_include.html" with show_agenda=True item=item schedule=schedule %}
{% else %}
{% for slide in item.session.slides %}
<a href="{{ slide.get_href }}">{{ slide.title|clean_whitespace }}</a>
<br>
{% endfor %}
{% endif %}
{% if item.slot_type.slug == 'other' %}
{% if item.session.agenda or item.session.remote_instructions or item.session.agenda_note %}
<div class="float-end ps-2">
{% include "meeting/session_buttons_include.html" with show_agenda=True item=item schedule=schedule %}
</div>
{% else %}
<div>
{% for slide in item.session.slides %}
<a href="{{ slide.get_href }}">{{ slide.title|clean_whitespace }}</a>
<br>
{% endfor %}
</div>
{% endif %}
</div>
{% endif %}
{% endif %}
</td>
</tr>
@ -300,7 +302,7 @@
<div class="timetooltip reschedtimetooltip">
<div data-start-time="{{ item.session.rescheduled_to.utc_start_time|date:"U" }}"
data-end-time="{{ item.session.rescheduled_to.utc_end_time|date:"U" }}"
{% if item.timeslot.time|date:"l" != item.session.rescheduled_to.time|date:"l" %} weekday="1"{% endif %}>
{% if item.timeslot.time|date:"l" != item.session.rescheduled_to.time|date:"l" %} data-weekday="1"{% endif %}>
{% if "-utc" in request.path %}
{{ item.session.rescheduled_to.utc_start_time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.utc_end_time|date:"G:i" }}
{% else %}
@ -313,7 +315,7 @@
{% endif %}
{% if item.session.agenda_note|first_url|conference_url %}
<br>
<a href={{ item.session.agenda_note|first_url }}>{{ item.session.agenda_note|slice:":23" }}
<a href="{{ item.session.agenda_note|first_url }}">{{ item.session.agenda_note|slice:":23" }}
</a>
{% elif item.session.agenda_note %}
<br>

View file

@ -17,7 +17,7 @@
{% endif %}
</td>
{% if session.all_meeting_sessions_cancelled %}
<td colspan="{% if user|has_role:'Secretariat' or user_groups %}7{% else %}6{% endif %}">
<td colspan="{% if user|has_role:'Secretariat' or user_groups %}6{% else %}5{% endif %}">
<span class="badge bg-danger">Session cancelled</span>
</td>
{% else %}
@ -121,4 +121,4 @@
</td>
{% endif %}
{% endif %}
</tr>
</tr>

View file

@ -42,7 +42,7 @@
{% endif %}
</td>
<td>
{{ d.name.desc|linkify|urlize_ietf_docs }}
{{ d.name.desc|urlize_ietf_docs|linkify }}
{% if first and d.name.slug == 'openreg' or first and d.name.slug == 'earlybird' %}
<a href="https://www.ietf.org/how/meetings/register/">Register here</a>.
{% endif %}

View file

@ -4,9 +4,9 @@
{% load textfilters %}
{% origin %}
{% with item=session.official_timeslotassignment acronym=session.historic_group.acronym %}
{% include "meeting/session_agenda_include.html" with slug=item.slug session=session timeslot=item.timeslot only %}
<div role="group" class="btn-group btn-group-sm">
{% if session.agenda and show_agenda %}
{% include "meeting/session_agenda_include.html" with slug=item.slug session=session timeslot=item.timeslot only %}
{# Note: if called with show_agenda=True, calling template must load agenda_materials.js, needed by session_agenda_include.html #}
{# agenda pop-up button #}
<a class="btn btn-outline-primary"
@ -157,4 +157,4 @@
{% endwith %}
{% endif %}
</div>
{% endwith %}
{% endwith %}

View file

@ -45,7 +45,7 @@
{% endif %}
{% endif %}
{% if meeting.type.slug == 'interim' and session.remote_instructions %}
<div>
<div class="my-3">
<b>Remote instructions:</b>
{% if session.agenda_note|first_url|conference_url %}
<a href="{{ session.agenda_note|first_url }}" title="Online conference">
@ -125,6 +125,7 @@
{% for pres in session.filtered_slides %}
<tr data-name="{{ pres.document.name }}" title="Drag to reorder.">
{% url 'ietf.doc.views_doc.document_main' name=pres.document.name as url %}
<td><i class="bi bi-grip-vertical"></i></td>
<td>
<a href="{{ pres.document.get_href }}">{{ pres.document.title }}</a>
<a href="{{ url }}">({{ pres.document.name }})</a>
@ -158,7 +159,7 @@
</a>
{% endif %}
{% if can_manage_materials %}<div class="small">Drag-and-drop to reorder slides</div>{% endif %}
{% if can_manage_materials %}<div class="form-text">Drag-and-drop to reorder slides</div>{% endif %}
<h3 class="mt-4">Drafts</h3>
<table class="table table-sm table-striped">
{% if session.filtered_drafts %}
@ -192,4 +193,4 @@
</a>
{% endif %}
{% endwith %}
{% endfor %}
{% endfor %}

View file

@ -29,7 +29,7 @@
{% endfor %}
</div>
{% endif %}
{% include 'meeting/tz-display.html' with meeting_timezone=None only %}
{% include 'meeting/tz-display.html' with id_suffix="" meeting_timezone=None only %}
{% include 'meeting/agenda_filter.html' with filter_categories=filter_categories customize_button_text="Customize the meeting list..." only %}
{% cache 600 upcoming-meetings entries.count %}
{% if entries %}
@ -152,4 +152,4 @@
agenda_filter.enable();
});
</script>
{% endblock %}
{% endblock %}

View file

@ -36,5 +36,5 @@
{{ message.subject|linkify }}
</dd>
</dl>
<pre>{{ message.body|linkify|urlize_ietf_docs }}</pre>
<pre>{{ message.body|urlize_ietf_docs|linkify }}</pre>
{% endblock %}

View file

@ -21,7 +21,7 @@
{% for position in positions %}
<div role="tabpanel" class="tab-pane {% if forloop.first %} active{% endif %}"
id="{{ position.name|slugify }}">
{{ position.get_questionnaire|linkify|urlize_ietf_docs|linebreaks }}
{{ position.get_questionnaire|urlize_ietf_docs|linkify|linebreaks }}
</div>
{% endfor %}
</div>

View file

@ -49,7 +49,7 @@
</dd>
{% endif %}
</dl>
<pre class="border p-3 pasted">{{ template.content|linkify|urlize_ietf_docs|linebreaksbr }}</pre>
<pre class="border p-3 pasted">{{ template.content|urlize_ietf_docs|linkify|linebreaksbr }}</pre>
<a class="btn btn-secondary my-3"
href="{% if return_url %}{{ return_url }}{% else %}../{% endif %}">Back</a>
{% endblock %}

View file

@ -13,7 +13,7 @@
{% origin %}
{% if persons|length > 1 %}
<p class="alert alert-warning my-3">
More than one person with this name has been found. Showing all:
More than one person with this name has been found. Showing all.
</p>
{% endif %}
{% for person in persons %}
@ -29,15 +29,18 @@
{% if person.photo %}
<div class="float-end ms-3 mb-3">{% include "person/photo.html" with person=person %}</div>
{% endif %}
{{ person.biography|linkify|urlize_ietf_docs|apply_markup:"restructuredtext" }}
{{ person.biography|apply_markup:"restructuredtext"|urlize_ietf_docs|linkify }}
</div>
{% if person.role_set.exists %}
<h2 class="mt-5" id="roles">Roles</h2>
<h2 class="mt-5" id="roles-{{ forloop.counter }}">Roles</h2>
{% if person.role_set.all|active_roles %}
<table class="table table-sm table-striped tablesorter">
<thead>
<th scope="col" data-sort="role">Role</th>
<th scope="col" data-sort="group">Group</th>
<th scope="col" data-sort="email">Email</th>
<tr>
<th scope="col" data-sort="role">Role</th>
<th scope="col" data-sort="group">Group</th>
<th scope="col" data-sort="email">Email</th>
</tr>
</thead>
<tbody>
{% for role in person.role_set.all|active_roles %}
@ -51,18 +54,21 @@
<a href="mailto:{{ role.email.address }}">{{ role.email.address }}</a>
</td>
</tr>
{% empty %}
{{ person.first_name }} has no active roles as of {{ today }}.
{% endfor %}
</tbody>
</table>
{% else %}
<p>{{ person.first_name }} has no active roles as of {{ today }}.</p>
{% endif %}
{% endif %}
{% if person.personextresource_set.exists %}
<h2 class="mt-5" id="extresources">External Resources</h2>
<h2 class="mt-5" id="extresources-{{ forloop.counter }}">External Resources</h2>
<table class="table table-sm table-striped tablesorter">
<thead>
<th scope="col" data-sort="name">Name</th>
<th scope="col" data-sort="value">Value</th>
<tr>
<th scope="col" data-sort="name">Name</th>
<th scope="col" data-sort="value">Value</th>
</tr>
</thead>
<tbody>
{% for extres in person.personextresource_set.all %}
@ -76,16 +82,18 @@
</tbody>
</table>
{% endif %}
<h2 class="mt-5" id="rfcs">
<h2 class="mt-5" id="rfcs-{{ forloop.counter }}">
RFCs <small class="text-muted">({{ person.rfcs|length }})</small>
</h2>
{% if person.rfcs %}
<table class="table table-sm table-striped tablesorter">
<thead>
<th scope="col" data-sort="rfc">RFC</th>
<th scope="col" data-sort="date">Date</th>
<th scope="col" data-sort="title">Title</th>
<th scope="col" data-sort="citedby">Cited by</th>
<tr>
<th scope="col" data-sort="rfc">RFC</th>
<th scope="col" data-sort="date">Date</th>
<th scope="col" data-sort="title">Title</th>
<th scope="col" data-sort="citedby">Cited by</th>
</tr>
</thead>
<tbody>
{% for doc in person.rfcs %}
@ -113,7 +121,7 @@
{% else %}
{{ person.first_name }} has no RFCs as of {{ today }}.
{% endif %}
<h2 class="mt-5" id="drafts">
<h2 class="mt-5" id="drafts-{{ forloop.counter }}">
Active Drafts <small class="text-muted">({{ person.active_drafts|length }})</small>
</h2>
{% if person.active_drafts.exists %}
@ -146,11 +154,11 @@
{% else %}
{{ person.first_name }} has no expired drafts as of {{ today }}.
{% endif %}
{% if persons|length == 1 and person.has_drafts %}
{% if person.has_drafts %}
<h2 class="mt-5">
Draft Activity
</h2>
<div id="chart">
<div id="chart-{{ forloop.counter }}">
</div>
{% endif %}
{% endfor %}
@ -161,15 +169,17 @@
<script src="{% static 'ietf/js/highcharts-exporting.js' %}"></script>
<script>
$(function () {
$.getJSON('{% url "ietf.doc.views_stats.chart_conf_person_drafts" id=persons.0.id %}', function (conf) {
// Create the chart
chart = Highcharts.stockChart('chart', conf);
chart.showLoading();
$.getJSON('{% url "ietf.doc.views_stats.chart_data_person_drafts" id=persons.0.id %}', function (data) {
chart.series[0].setData(data);
chart.hideLoading();
{% for person in persons %}
$.getJSON('{% url "ietf.doc.views_stats.chart_conf_person_drafts" id=person.id %}', function (conf) {
// Create the chart
chart = Highcharts.stockChart('chart-{{ forloop.counter }}', conf);
chart.showLoading();
$.getJSON('{% url "ietf.doc.views_stats.chart_data_person_drafts" id=person.id %}', function (data) {
chart.series[0].setData(data);
chart.hideLoading();
});
});
});
{% endfor %}
});
</script>
{% endblock %}

View file

@ -35,7 +35,7 @@
Message
</dt>
<dd class="col-sm-10">
<pre>{{ message.message.body|linkify|urlize_ietf_docs|linebreaksbr }}</pre>
<pre>{{ message.message.body|urlize_ietf_docs|linkify|linebreaksbr }}</pre>
</dd>
<dt class="col-sm-2">
Attachment

View file

@ -94,7 +94,7 @@
aria-label="Close"></button>
</div>
<div class="modal-body" id="{{ check.checker|slugify }}-message">
<pre>{{ check.message|linkify|urlize_ietf_docs }}</pre>
<pre>{{ check.message|urlize_ietf_docs|linkify }}</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>

View file

@ -51,6 +51,8 @@ import subprocess
import tempfile
import copy
import factory.random
import urllib3
from urllib.parse import urlencode
from fnmatch import fnmatch
from pathlib import Path
@ -95,6 +97,108 @@ template_coverage_collection = None
code_coverage_collection = None
url_coverage_collection = None
def start_vnu_server(port=8888):
"Start a vnu validation server on the indicated port"
vnu = subprocess.Popen(
[
"java",
"-Dnu.validator.servlet.bind-address=127.0.0.1",
"-Dnu.validator.servlet.max-file-size=16777216",
"-cp",
"bin/vnu.jar",
"nu.validator.servlet.Main",
f"{port}",
],
stdout=subprocess.DEVNULL,
)
print(" Waiting for vnu server to start up...", end="")
while vnu_validate(b"", content_type="", port=port) is None:
print(".", end="")
time.sleep(1)
print()
return vnu
http = urllib3.PoolManager(retries=urllib3.Retry(99, redirect=False))
def vnu_validate(html, content_type="text/html", port=8888):
"Pass the HTML to the vnu server running on the indicated port"
gzippeddata = gzip.compress(html)
try:
req = http.request(
"POST",
f"http://127.0.0.1:{port}/?"
+ urlencode({"out": "json", "asciiquotes": "yes"}),
headers={
"Content-Type": content_type,
"Accept-Encoding": "gzip",
"Content-Encoding": "gzip",
"Content-Length": str(len(gzippeddata)),
},
body=gzippeddata,
)
except (
urllib3.exceptions.NewConnectionError,
urllib3.exceptions.MaxRetryError,
ConnectionRefusedError,
):
return None
assert req.status == 200
return req.data.decode("utf-8")
def vnu_fmt_message(file, msg, content):
"Convert a vnu JSON message into a printable string"
ret = f"\n{file}:\n"
if "extract" in msg:
ret += msg["extract"].replace("\n", " ") + "\n"
ret += " " * msg["hiliteStart"]
ret += "^" * msg["hiliteLength"] + "\n"
ret += " " * msg["hiliteStart"]
ret += f"{msg['type']}: {msg['message']}\n"
if "firstLine" in msg and "lastLine" in msg:
ret += f'Source snippet, lines {msg["firstLine"]-5} to {msg["lastLine"]+4}:\n'
lines = content.splitlines()
for line in range(msg["firstLine"] - 5, msg["lastLine"] + 5):
ret += f"{line}: {lines[line]}\n"
return ret
def vnu_filter_message(msg, filter_db_issues, filter_test_issues):
"True if the vnu message is a known false positive"
if filter_db_issues and re.search(
r"""^Forbidden\ code\ point\ U\+|
'href'\ on\ element\ 'a':\ Percentage\ \("%"\)\ is\ not\ followed|
^Saw\ U\+\d+\ in\ stream|
^Document\ uses\ the\ Unicode\ Private\ Use\ Area""",
msg["message"],
flags=re.VERBOSE,
):
return True
if filter_test_issues and re.search(
r"""Ceci\ n'est\ pas\ une\ URL|
^The\ '\w+'\ attribute\ on\ the\ '\w+'\ element\ is\ obsolete|
^Section\ lacks\ heading|
is\ not\ in\ Unicode\ Normalization\ Form\ C""",
msg["message"],
flags=re.VERBOSE,
):
return True
return re.search(
r"""document\ is\ not\ mappable\ to\ XML\ 1|
^Attribute\ 'required'\ not\ allowed\ on\ element\ 'div'|
^The\ 'type'\ attribute\ is\ unnecessary\ for\ JavaScript""",
msg["message"],
flags=re.VERBOSE,
)
def load_and_run_fixtures(verbosity):
loadable = [f for f in settings.GLOBAL_TEST_FIXTURES if "." not in f]
call_command('loaddata', *loadable, verbosity=int(verbosity)-1, database="default")
@ -196,7 +300,7 @@ class ValidatingTemplate(Template):
return content
fingerprint = hash(content) + sys.maxsize + 1 # make hash positive
if fingerprint in self.backend.validation_cache:
if not settings.validate_html_harder and fingerprint in self.backend.validation_cache:
# already validated this HTML fragment, skip it
# as an optimization, make page a bit smaller by not returning HTML for the menus
# FIXME: figure out why this still includes base/menu.html
@ -590,8 +694,11 @@ class IetfTestRunner(DiscoverRunner):
parser.add_argument('--no-validate-html',
action='store_false', dest="validate_html", default=True,
help='Do not validate all generated HTML with html-validate.org')
parser.add_argument('--validate-html-harder',
action='store_true', dest="validate_html_harder", default=False,
help='Validate all generated HTML with additional validators (slow)')
def __init__(self, skip_coverage=False, save_version_coverage=None, html_report=None, permit_mixed_migrations=None, show_logging=None, validate_html=None, **kwargs):
def __init__(self, skip_coverage=False, save_version_coverage=None, html_report=None, permit_mixed_migrations=None, show_logging=None, validate_html=None, validate_html_harder=None, **kwargs):
#
self.check_coverage = not skip_coverage
self.save_version_coverage = save_version_coverage
@ -599,6 +706,7 @@ class IetfTestRunner(DiscoverRunner):
self.permit_mixed_migrations = permit_mixed_migrations
self.show_logging = show_logging
settings.validate_html = self if validate_html else None
settings.validate_html_harder = self if validate_html and validate_html_harder else None
settings.show_logging = show_logging
#
self.root_dir = os.path.dirname(settings.BASE_DIR)
@ -718,7 +826,7 @@ class IetfTestRunner(DiscoverRunner):
print(" Not validating any generated HTML; "
"please do so at least once before committing changes")
else:
print(" Validating all HTML generated during the tests")
print(" Validating all HTML generated during the tests", end="")
self.batches = {"doc": [], "frag": []}
# keep the html-validate configs here, so they can be kept in sync easily
@ -768,6 +876,13 @@ class IetfTestRunner(DiscoverRunner):
self.config_file[kind].flush()
Path(self.config_file[kind].name).chmod(0o644)
if not settings.validate_html_harder:
print("")
self.vnu = None
else:
print(" (extra pedantically)")
self.vnu = start_vnu_server()
super(IetfTestRunner, self).setup_test_environment(**kwargs)
def teardown_test_environment(self, **kwargs):
@ -789,12 +904,18 @@ class IetfTestRunner(DiscoverRunner):
else:
with open(self.coverage_file, "w") as file:
json.dump(self.coverage_master, file, indent=2, sort_keys=True)
super(IetfTestRunner, self).teardown_test_environment(**kwargs)
if settings.validate_html:
for kind in self.batches:
self.validate(kind)
try:
self.validate(kind)
except Exception:
pass
self.config_file[kind].close()
if self.vnu:
self.vnu.terminate()
super(IetfTestRunner, self).teardown_test_environment(**kwargs)
def validate(self, kind):
if not self.batches[kind]:
@ -843,9 +964,10 @@ class IetfTestRunner(DiscoverRunner):
errors += (
f'\n{result["filePath"]}:\n'
+ "".join(source_lines[line - 5 : line])
+ "-" * (msg["column"] - 1)
+ "^" * msg["size"]
+ f' {msg["ruleId"]}: {msg["message"]} '
+ " " * (msg["column"] - 1)
+ "^" * msg["size"] + "\n"
+ " " * (msg["column"] - 1)
+ f'{msg["ruleId"]}: {msg["message"]} '
+ f'on line {line}:{msg["column"]}\n'
+ "".join(source_lines[line : line + 5])
+ "\n"
@ -853,6 +975,28 @@ class IetfTestRunner(DiscoverRunner):
if errors:
testcase.fail(errors)
if settings.validate_html_harder:
if kind == "frag":
return
files = [
os.path.join(d, f)
for d, dirs, files in os.walk(tmpdir.name)
for f in files
]
for file in files:
with open(file, "rb") as f:
content = f.read()
result = vnu_validate(content)
assert result
for msg in json.loads(result)["messages"]:
if vnu_filter_message(msg, False, True):
continue
errors = vnu_fmt_message(file, msg, content)
if errors:
testcase.fail(errors)
tmpdir.cleanup()
def get_test_paths(self, test_labels):

View file

@ -17,8 +17,10 @@ import debug # pyflakes:ignore
from .texescape import init as texescape_init, tex_escape_map
tlds_sorted = sorted(tlds.tld_set, key=len, reverse=True)
protocols = bleach.sanitizer.ALLOWED_PROTOCOLS
protocols.append("ftp") # we still have some ftp links
bleach_linker = bleach.Linker(
url_re=bleach.linkifier.build_url_re(tlds=tlds_sorted),
url_re=bleach.linkifier.build_url_re(tlds=tlds_sorted, protocols=protocols),
email_re=bleach.linkifier.build_email_re(tlds=tlds_sorted), # type: ignore
parse_email=True
)

View file

@ -152,7 +152,6 @@
"ietf/secr/static/images/tooltag-arrowright.webp",
"ietf/secr/static/images/tooltag-arrowright_over.webp",
"ietf/secr/static/js/dynamic_inlines.js",
"ietf/secr/static/js/proceedings-recording.js",
"ietf/secr/static/js/session_form.js",
"ietf/secr/static/js/sessions.js",
"ietf/secr/static/js/utils.js"