diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py index aecb7ee809..66337bff20 100644 --- a/ietf/ipr/tests.py +++ b/ietf/ipr/tests.py @@ -209,6 +209,24 @@ def test_search(self): r = self.client.get(url + "?submit=iprtitle&iprtitle=%s" % quote(ipr.title)) self.assertContains(r, ipr.title) + def test_search_null_characters(self): + """IPR search gracefully rejects null characters in parameters""" + # Not a combinatorially exhaustive set, but tries to exercise all the parameters + bad_params = [ + "option=document_search&document_search=draft-\x00stuff" + "submit=dra\x00ft", + "submit=draft&id=some\x00id", + "submit=draft&id_document_tag=some\x00id", + "submit=draft&id=someid&state=re\x00moved", + "submit=draft&id=someid&state=posted&state=re\x00moved", + "submit=draft&id=someid&state=removed&draft=draft-no\x00tvalid", + "submit=rfc&rfc=rfc\x00123", + ] + url = urlreverse("ietf.ipr.views.search") + for query_params in bad_params: + r = self.client.get(f"{url}?{query_params}") + self.assertEqual(r.status_code, 400, f"querystring '{query_params}' should be rejected") + def test_feed(self): ipr = HolderIprDisclosureFactory() r = self.client.get("/feed/ipr/") diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py index e8cdc0c105..e2ddb3bcc3 100644 --- a/ietf/ipr/views.py +++ b/ietf/ipr/views.py @@ -10,7 +10,7 @@ from django.db.models import Q from django.forms.models import inlineformset_factory, model_to_dict from django.forms.formsets import formset_factory -from django.http import HttpResponse, Http404, HttpResponseRedirect +from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseBadRequest from django.shortcuts import render, get_object_or_404, redirect from django.template.loader import render_to_string from django.urls import reverse as urlreverse @@ -629,11 +629,16 @@ def post(request, id): def search(request): search_type = request.GET.get("submit") + if search_type and "\x00" in search_type: + return HttpResponseBadRequest("Null characters are not allowed") + # query field q = '' # legacy support if not search_type and request.GET.get("option", None) == "document_search": docname = request.GET.get("document_search", "") + if docname and "\x00" in docname: + return HttpResponseBadRequest("Null characters are not allowed") if docname.startswith("draft-"): search_type = "draft" q = docname @@ -643,18 +648,24 @@ def search(request): if search_type: form = SearchForm(request.GET) docid = request.GET.get("id") or request.GET.get("id_document_tag") or "" + if docid and "\x00" in docid: + return HttpResponseBadRequest("Null characters are not allowed") docs = doc = None iprs = [] related_iprs = [] # set states states = request.GET.getlist('state',settings.PUBLISH_IPR_STATES) + if any("\x00" in state for state in states if state): + return HttpResponseBadRequest("Null characters are not allowed") if states == ['all']: states = IprDisclosureStateName.objects.values_list('slug',flat=True) # get query field if request.GET.get(search_type): q = request.GET.get(search_type) + if q and "\x00" in q: + return HttpResponseBadRequest("Null characters are not allowed") if q or docid: # Search by RFC number or draft-identifier @@ -664,13 +675,12 @@ def search(request): if docid: start = DocAlias.objects.filter(name__iexact=docid) - else: - if search_type == "draft": - q = normalize_draftname(q) - start = DocAlias.objects.filter(name__icontains=q, name__startswith="draft") - elif search_type == "rfc": - start = DocAlias.objects.filter(name="rfc%s" % q.lstrip("0")) - + elif search_type == "draft": + q = normalize_draftname(q) + start = DocAlias.objects.filter(name__icontains=q, name__startswith="draft") + else: # search_type == "rfc" + start = DocAlias.objects.filter(name="rfc%s" % q.lstrip("0")) + # one match if len(start) == 1: first = start[0]