Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion httplint/field/parsers/accept.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def evaluate(self, add_note: AddNoteMethodType) -> None:
class BAD_Q_VALUE(Note):
category = categories.CONNEG
level = levels.WARN
_summary = "The q value on '{media_type}' is invalid."
_summary = "The q value on '%(media_type)s' is invalid."
_text = """\
The `q` parameter must be a decimal number between 0 and 1, with at most 3 digits of precision."""

Expand Down
21 changes: 14 additions & 7 deletions httplint/field/parsers/accept_patch.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import Tuple

from httplint.field import BAD_SYNTAX
from httplint.field.list_field import HttpListField
from httplint.field.tests import FieldTest
from httplint.field.utils import parse_media_type
from httplint.note import Note, categories, levels
from httplint.syntax import rfc9110
from httplint.types import (
AddNoteMethodType,
NoteClassListType,
Expand All @@ -18,16 +20,12 @@ class accept_patch(HttpListField[ResponseLinterProtocol]):
The `Accept-Patch` response header advertises which media types are accepted by the server in a
PATCH request."""
reference = "https://www.rfc-editor.org/rfc/rfc5789.html#section-3.1"
syntax = False
syntax = rfc9110.list_rule(rfc9110.media_type, 1)
category = categories.GENERAL
deprecated = False

def parse(
self, field_value: str, add_note: AddNoteMethodType
) -> Tuple[str, ParamDictType]:
return parse_media_type(
field_value, add_note, ACCEPT_PATCH_BAD_SYNTAX, self.reference
)
def parse(self, field_value: str, add_note: AddNoteMethodType) -> Tuple[str, ParamDictType]:
return parse_media_type(field_value, add_note, ACCEPT_PATCH_BAD_SYNTAX, self.reference)


class ACCEPT_PATCH_BAD_SYNTAX(Note):
Expand Down Expand Up @@ -59,4 +57,13 @@ class AcceptPatchBadTest(FieldTest[ResponseLinterProtocol]):
name = "Accept-Patch"
inputs = [b"invalid"]
expected_out = [("invalid", {})]
expected_notes: NoteClassListType = [ACCEPT_PATCH_BAD_SYNTAX, BAD_SYNTAX]


class AcceptPatchWildcardTest(FieldTest[ResponseLinterProtocol]):
"Accept-Patch lists media types, not media ranges."

name = "Accept-Patch"
inputs = [b"*/*"]
expected_out = [("*/*", {})]
expected_notes: NoteClassListType = [ACCEPT_PATCH_BAD_SYNTAX]
11 changes: 6 additions & 5 deletions httplint/field/parsers/accept_post.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import Tuple

from httplint.field import BAD_SYNTAX
from httplint.field.list_field import HttpListField
from httplint.field.tests import FieldTest
from httplint.field.utils import parse_media_type
from httplint.note import Note, categories, levels
from httplint.syntax import rfc9110
from httplint.types import (
AddNoteMethodType,
NoteClassListType,
Expand All @@ -18,13 +20,12 @@ class accept_post(HttpListField[ResponseLinterProtocol]):
The `Accept-Post` response header advertises which media types are accepted by the server in a
POST request."""
reference = "https://www.w3.org/TR/ldp/#header-accept-post"
syntax = False
# LDP defines this as #media-range, not #media-type; wildcards are allowed.
syntax = rfc9110.list_rule(rfc9110.media_range)
category = categories.GENERAL
deprecated = False

def parse(
self, field_value: str, add_note: AddNoteMethodType
) -> Tuple[str, ParamDictType]:
def parse(self, field_value: str, add_note: AddNoteMethodType) -> Tuple[str, ParamDictType]:
return parse_media_type(
field_value,
add_note,
Expand Down Expand Up @@ -60,4 +61,4 @@ class AcceptPostBadTest(FieldTest[ResponseLinterProtocol]):
name = "Accept-Post"
inputs = [b"invalid"]
expected_out = [("invalid", {})]
expected_notes: NoteClassListType = [ACCEPT_POST_BAD_SYNTAX]
expected_notes: NoteClassListType = [ACCEPT_POST_BAD_SYNTAX, BAD_SYNTAX]
118 changes: 96 additions & 22 deletions httplint/field/parsers/accept_query.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,75 @@
from typing import Tuple
from http_sf import Token

from httplint.field.list_field import HttpListField
from httplint.field.structured_field import StructuredField
from httplint.field.tests import FieldTest
from httplint.field.utils import parse_media_type
from httplint.field.utils import check_media_type
from httplint.note import Note, categories, levels
from httplint.types import (
AddNoteMethodType,
NoteClassListType,
ParamDictType,
ResponseLinterProtocol,
SFListType,
)

SPEC_URL = "https://www.rfc-editor.org/rfc/rfc10008.html"

class accept_query(HttpListField[ResponseLinterProtocol]):

class accept_query(StructuredField[ResponseLinterProtocol]):
canonical_name = "Accept-Query"
description = """\
The `Accept-Query` response header advertises which media types are accepted by the server in the
content of a QUERY request."""
reference = (
"https://datatracker.ietf.org/doc/html/"
"draft-ietf-httpbis-safe-method-w-body#section-3"
)
syntax = False
reference = f"{SPEC_URL}#section-3"
syntax = False # Structured Field
category = categories.GENERAL
deprecated = False
sf_type = "list"
value: SFListType

def evaluate(self, add_note: AddNoteMethodType) -> None:
normalised: SFListType = []
for item in self.value:
# SF List items are (value, parameters) tuples
val, params = item
if not isinstance(val, (Token, str)):
add_note(ACCEPT_QUERY_BAD_TYPE, value=str(val))
normalised.append(item)
continue
# Media type parameters are carried as SF parameters, so the item
# value is the media range on its own. Media ranges are
# case-insensitive, so store them lowercased, as the other
# media-type fields do.
media_range = str(val).lower()
check_media_type(
media_range,
add_note,
ACCEPT_QUERY_BAD_SYNTAX,
self.reference,
allow_wildcard=True,
check_token=True, # no syntax check on a Structured Field
)
normalised.append((type(val)(media_range), params))
self.value = normalised

def parse(
self, field_value: str, add_note: AddNoteMethodType
) -> Tuple[str, ParamDictType]:
return parse_media_type(
field_value, add_note, ACCEPT_QUERY_BAD_SYNTAX, self.reference
)

class ACCEPT_QUERY_BAD_TYPE(Note):
category = categories.GENERAL
level = levels.BAD
_summary = "The Accept-Query header contains a value that isn't a media range."
_text = """\
`Accept-Query` is a List Structured Field whose members are Tokens or Strings, each
naming a media range accepted in the content of a QUERY request. `%(value)s` is
neither, so it will be ignored."""


class ACCEPT_QUERY_BAD_SYNTAX(Note):
category = categories.GENERAL
level = levels.BAD
_summary = "The Accept-Query header contains a value that is not a media type."
_summary = "The Accept-Query header contains a value that is not a media range."
_text = """\
`%(value)s` is not a valid media type. `Accept-Query` is a list of media types
(e.g., `application/sparql-query`) accepted in the content of a QUERY request;
see [its definition](%(ref_uri)s) for more information."""
`%(value)s` is not a valid media range. `Accept-Query` is a list of media ranges
(e.g., `application/sparql-query`, `text/*`) accepted in the content of a QUERY
request; see [its definition](%(ref_uri)s) for more information."""


class AcceptQueryTest(FieldTest[ResponseLinterProtocol]):
Expand All @@ -49,14 +78,59 @@ class AcceptQueryTest(FieldTest[ResponseLinterProtocol]):
expected_out = [("application/sparql-query", {}), ("application/sql", {})]


class AcceptQueryStringTest(FieldTest[ResponseLinterProtocol]):
"Media types that aren't valid Tokens have to be sent as Strings."

name = "Accept-Query"
inputs = [b'"application/jsonpath", "3d/example"']
expected_out = [("application/jsonpath", {}), ("3d/example", {})]


class AcceptQueryParamsTest(FieldTest[ResponseLinterProtocol]):
name = "Accept-Query"
inputs = [b"application/example;version=1"]
expected_out = [("application/example", {"version": "1"})]
inputs = [b'application/sql;charset="UTF-8"']
expected_out = [("application/sql", {"charset": "UTF-8"})]


class AcceptQueryWildcardTest(FieldTest[ResponseLinterProtocol]):
name = "Accept-Query"
inputs = [b"*/*, text/*"]
expected_out = [("*/*", {}), ("text/*", {})]


class AcceptQueryCaseTest(FieldTest[ResponseLinterProtocol]):
name = "Accept-Query"
inputs = [b'APPLICATION/SQL, "TEXT/Plain"']
expected_out = [("application/sql", {}), ("text/plain", {})]


class AcceptQueryBadTest(FieldTest[ResponseLinterProtocol]):
name = "Accept-Query"
inputs = [b"invalid"]
expected_out = [("invalid", {})]
expected_notes: NoteClassListType = [ACCEPT_QUERY_BAD_SYNTAX]


class AcceptQueryBadStringTest(FieldTest[ResponseLinterProtocol]):
"A String member can carry a name that isn't a valid HTTP token."

name = "Accept-Query"
inputs = [b'"text/pl in"']
expected_out = [("text/pl in", {})]
expected_notes: NoteClassListType = [ACCEPT_QUERY_BAD_SYNTAX]


class AcceptQueryBareStarTest(FieldTest[ResponseLinterProtocol]):
"Only */* and type/* are permitted, not a bare *."

name = "Accept-Query"
inputs = [b"*"]
expected_out = [("*", {})]
expected_notes: NoteClassListType = [ACCEPT_QUERY_BAD_SYNTAX]


class AcceptQueryBadTypeTest(FieldTest[ResponseLinterProtocol]):
name = "Accept-Query"
inputs = [b"123"]
expected_out = [(123, {})]
expected_notes: NoteClassListType = [ACCEPT_QUERY_BAD_TYPE]
49 changes: 47 additions & 2 deletions httplint/field/parsers/content_type.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from typing import Tuple
from typing import Any, Tuple

from httplint.field.singleton_field import SingletonField
from httplint.field.tests import FieldTest
from httplint.field.utils import parse_media_type
from httplint.field.utils import (
MEDIA_TYPE_BAD_NAME,
MEDIA_TYPE_LONG_NAME,
parse_media_type,
)
from httplint.syntax import rfc9110
from httplint.types import (
AddNoteMethodType,
AnyMessageLinterProtocol,
NoteClassListType,
ParamDictType,
)

Expand All @@ -29,3 +34,43 @@ class BasicCTTest(FieldTest[AnyMessageLinterProtocol]):
name = "Content-Type"
inputs = [b"text/plain; charset=utf-8"]
expected_out = ("text/plain", {"charset": "utf-8"})


class CTSuffixTest(FieldTest[AnyMessageLinterProtocol]):
name = "Content-Type"
inputs = [b"application/vnd.example.foo-bar+json"]
expected_out: Any = ("application/vnd.example.foo-bar+json", {})


class CTBadNameTest(FieldTest[AnyMessageLinterProtocol]):
"A media type that's a valid HTTP token, but not a valid RFC 6838 name."

name = "Content-Type"
inputs = [b"text/pl~in"]
expected_out: Any = ("text/pl~in", {})
expected_notes: NoteClassListType = [MEDIA_TYPE_BAD_NAME]


class CTBadTypeNameTest(FieldTest[AnyMessageLinterProtocol]):
"The type half is checked as well as the subtype half."

name = "Content-Type"
inputs = [b"~text/plain"]
expected_out: Any = ("~text/plain", {})
expected_notes: NoteClassListType = [MEDIA_TYPE_BAD_NAME]


class CTBadNameFirstTest(FieldTest[AnyMessageLinterProtocol]):
"RFC 6838 names have to start with a letter or a digit."

name = "Content-Type"
inputs = [b"text/.plain"]
expected_out: Any = ("text/.plain", {})
expected_notes: NoteClassListType = [MEDIA_TYPE_BAD_NAME]


class CTLongNameTest(FieldTest[AnyMessageLinterProtocol]):
name = "Content-Type"
inputs = [b"text/" + b"a" * 128]
expected_out: Any = ("text/" + "a" * 128, {})
expected_notes: NoteClassListType = [MEDIA_TYPE_LONG_NAME]
Loading