diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 1947fe272a..83a152c148 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -3648,8 +3648,23 @@ def test_obsoleted(self): def test_generate_idnits2_rfc_status(self): for slug in ('bcp', 'ds', 'exp', 'hist', 'inf', 'std', 'ps', 'unkn'): WgRfcFactory(std_level_id=slug) + WgRfcFactory(rfc_number=10001, std_level_id='ps') + WgRfcFactory(rfc_number=10002, std_level_id=None) blob = generate_idnits2_rfc_status().replace("\n", "") self.assertEqual(blob[6312-1], "O") + # idnits2 discards the whole file if this is not "O" - see generate_idnits2_rfc_status + self.assertEqual(blob[16-1], "O") + self.assertEqual(blob[10001-1], "P") + self.assertEqual(blob[10002-1], "U") + + def test_generate_idnits2_rfc_status_low_numbers_only(self): + # The workarounds write fixed offsets, so the blob has to reach 6312 even when + # no RFC does. + WgRfcFactory(rfc_number=1001, std_level_id="ps") + blob = generate_idnits2_rfc_status().replace("\n", "") + self.assertEqual(blob[6312-1], "O") + self.assertEqual(blob[200-1], "O") + self.assertEqual(blob[16-1], "O") def test_rfc_status(self): url = urlreverse('ietf.doc.views_doc.idnits2_rfc_status') diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index a353cfa3f2..301c352a0d 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -21,7 +21,7 @@ from django.conf import settings from django.contrib import messages from django.core.cache import caches -from django.db.models import OuterRef +from django.db.models import Max, OuterRef from django.forms import ValidationError from django.http import Http404 from django.template.loader import render_to_string @@ -1386,8 +1386,6 @@ def update_doc_extresources(doc, new_resources, by): def generate_idnits2_rfc_status(): - blob=['N']*10000 - symbols={ 'ps': 'P', 'inf': 'I', @@ -1399,10 +1397,17 @@ def generate_idnits2_rfc_status(): 'unkn': 'U', } - rfcs = Document.objects.filter(type_id='rfc') + rfcs = Document.objects.filter(type_id='rfc').exclude(rfc_number=None) + + # One character per RFC number, indexed by that number, so the array has to reach the + # highest RFC published. The floor keeps the fixed offsets in the workarounds below in + # range when only a few RFCs exist, as is the case under test. + highest = rfcs.aggregate(Max('rfc_number'))['rfc_number__max'] or 0 + blob=['N']*max(highest, 6312) + for rfc in rfcs: offset = int(rfc.rfc_number)-1 - blob[offset] = symbols[rfc.std_level_id] + blob[offset] = symbols.get(rfc.std_level_id, 'U') if rfc.related_that('obs'): blob[offset] = 'O' @@ -1422,6 +1427,17 @@ def generate_idnits2_rfc_status(): # RFC200 is an old RFC List by Number blob[200 -1] = 'O' + # !! Do not remove: idnits2 rejects this entire file if RFC16 is not 'O' !! + # + # This deliberately contradicts both the datatracker and the RFC Editor, which + # record RFC16 as updated rather than obsoleted. idnits2 validates its download + # of this file by matching the first 64 characters against a literal pattern that + # asserts 'O' here, inherited from a tools.ietf.org curation that disagreed with + # the RFC Editor. A mismatch makes idnits2 discard the file as corrupt and fall + # back to whatever stale copy it has, silently performing no RFC status checks at + # all. Removing this line therefore breaks every idnits2 client, not just RFC16. + blob[16 - 1] = 'O' + # End Workarounds blob = re.sub('N*$','',''.join(blob))