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
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ jobs:
uses: actions/checkout@v7

- name: Initialize CodeQL
uses: github/codeql-action/init@v4.37.0
uses: github/codeql-action/init@v4.37.1
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql.yml
queries: +security-and-quality

- name: Autobuild
uses: github/codeql-action/autobuild@v4.37.0
uses: github/codeql-action/autobuild@v4.37.1
if: ${{ matrix.language == 'python' || matrix.language == 'javascript' }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4.37.0
uses: github/codeql-action/analyze@v4.37.1
with:
category: "/language:${{ matrix.language }}"
4 changes: 4 additions & 0 deletions CHANGES/13054.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fixed ``escape_quotes`` in the Digest authentication middleware not escaping
backslashes, so a ``WWW-Authenticate`` challenge value containing a backslash
could break out of its quoted-string in the generated ``Authorization`` header
-- by :user:`dxbjavid`.
3 changes: 3 additions & 0 deletions CHANGES/13171.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed the C HTTP parser folding the fragment into the query string for an
origin-form request target with an empty query (e.g. ``/path?#frag``),
which diverged from the pure-Python parser -- by :user:`GiulioDER`.
1 change: 1 addition & 0 deletions CHANGES/13174.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed the C parser reporting newer HTTP methods such as ``QUERY`` as ``<unknown>``; the method table is now derived from the vendored llhttp instead of a hand-maintained count -- by :user:`Dreamsorcerer`.
3 changes: 3 additions & 0 deletions CHANGES/13176.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Exposed the request's timeout configuration to client middlewares as the
read-only :attr:`ClientRequest.timeout <aiohttp.ClientRequest.timeout>`
property -- by :user:`rodrigobnogueira`.
7 changes: 7 additions & 0 deletions CHANGES/5303.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Fixed :py:attr:`~aiohttp.web.StreamResponse.last_modified` rounding a
:class:`datetime.datetime` with a fractional second down while rounding an
equivalent :class:`float` timestamp up, so the two produced different
``Last-Modified`` headers for the same instant. Both now round up to the
next whole second, consistent with how :py:class:`~aiohttp.web.FileResponse`
relies on this rounding to avoid falsely reporting a resource as
unmodified.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ Jan Buchar
Jan Gosmann
Jarno Elonen
Jashandeep Sohi
Javid Khan
Javier Torres
Jean-Baptiste Estival
Jens Steinhauser
Expand Down
82 changes: 6 additions & 76 deletions aiohttp/_cparser.pxd
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
from libc.stdint cimport int32_t, uint8_t, uint16_t, uint64_t
# This file contains just the definitions needed in our Cython code.

from libc.stdint cimport uint8_t, uint16_t, uint64_t


cdef extern from "llhttp.h":

struct llhttp__internal_s:
int32_t _index
void* _span_pos0
void* _span_cb0
int32_t error
const char* reason
const char* error_pos
void* data
void* _current
uint64_t content_length
uint8_t type
uint8_t method
uint8_t http_major
uint8_t http_minor
uint8_t header_state
uint8_t lenient_flags
uint8_t upgrade
uint8_t finish
uint16_t flags
uint16_t status_code
void* settings

ctypedef llhttp__internal_s llhttp__internal_t
ctypedef llhttp__internal_t llhttp_t
Expand All @@ -43,20 +34,10 @@ cdef extern from "llhttp.h":
llhttp_cb on_chunk_header
llhttp_cb on_chunk_complete

llhttp_cb on_url_complete
llhttp_cb on_status_complete
llhttp_cb on_header_field_complete
llhttp_cb on_header_value_complete

ctypedef llhttp_settings_s llhttp_settings_t

enum llhttp_errno:
HPE_OK,
HPE_INTERNAL,
HPE_STRICT,
HPE_LF_EXPECTED,
HPE_UNEXPECTED_CONTENT_LENGTH,
HPE_CLOSED_CONNECTION,
HPE_INVALID_METHOD,
HPE_INVALID_URL,
HPE_INVALID_CONSTANT,
Expand All @@ -73,8 +54,7 @@ cdef extern from "llhttp.h":
HPE_CB_CHUNK_HEADER,
HPE_CB_CHUNK_COMPLETE,
HPE_PAUSED,
HPE_PAUSED_UPGRADE,
HPE_USER
HPE_PAUSED_UPGRADE

ctypedef llhttp_errno llhttp_errno_t

Expand All @@ -84,58 +64,10 @@ cdef extern from "llhttp.h":

enum llhttp_type:
HTTP_REQUEST,
HTTP_RESPONSE,
HTTP_BOTH
HTTP_RESPONSE

enum llhttp_method:
HTTP_DELETE,
HTTP_GET,
HTTP_HEAD,
HTTP_POST,
HTTP_PUT,
HTTP_CONNECT,
HTTP_OPTIONS,
HTTP_TRACE,
HTTP_COPY,
HTTP_LOCK,
HTTP_MKCOL,
HTTP_MOVE,
HTTP_PROPFIND,
HTTP_PROPPATCH,
HTTP_SEARCH,
HTTP_UNLOCK,
HTTP_BIND,
HTTP_REBIND,
HTTP_UNBIND,
HTTP_ACL,
HTTP_REPORT,
HTTP_MKACTIVITY,
HTTP_CHECKOUT,
HTTP_MERGE,
HTTP_MSEARCH,
HTTP_NOTIFY,
HTTP_SUBSCRIBE,
HTTP_UNSUBSCRIBE,
HTTP_PATCH,
HTTP_PURGE,
HTTP_MKCALENDAR,
HTTP_LINK,
HTTP_UNLINK,
HTTP_SOURCE,
HTTP_PRI,
HTTP_DESCRIBE,
HTTP_ANNOUNCE,
HTTP_SETUP,
HTTP_PLAY,
HTTP_PAUSE,
HTTP_TEARDOWN,
HTTP_GET_PARAMETER,
HTTP_SET_PARAMETER,
HTTP_REDIRECT,
HTTP_RECORD,
HTTP_FLUSH

ctypedef llhttp_method llhttp_method_t;
HTTP_CONNECT

void llhttp_settings_init(llhttp_settings_t* settings)
void llhttp_init(llhttp_t* parser, llhttp_type type,
Expand All @@ -152,8 +84,6 @@ cdef extern from "llhttp.h":
const char* llhttp_get_error_reason(const llhttp_t* parser)
const char* llhttp_get_error_pos(const llhttp_t* parser)

const char* llhttp_method_name(llhttp_method_t method)

void llhttp_set_lenient_headers(llhttp_t* parser, int enabled)
void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled)
void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled)
31 changes: 20 additions & 11 deletions aiohttp/_http_parser.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,29 @@ cdef inline object extend(object buf, const char* at, size_t length):
memcpy(ptr + s, at, length)


DEF METHODS_COUNT = 46;
# The method-name table and its length come straight from llhttp's canonical
# HTTP_ALL_METHOD_MAP, so they track the vendored llhttp version automatically
# instead of relying on a hand-maintained method count.
cdef extern from *:
"""
#include "llhttp.h"

#define _AIOHTTP_METHOD_NAME(NUM, NAME, STRING) [NUM] = #STRING,
static const char* const _aiohttp_method_names[] = {
HTTP_ALL_METHOD_MAP(_AIOHTTP_METHOD_NAME)
};
#undef _AIOHTTP_METHOD_NAME
"""
const char* _aiohttp_method_names[]
const int METHODS_COUNT "((int)(sizeof(_aiohttp_method_names) / sizeof(_aiohttp_method_names[0])))"


cdef list _http_method = []

for i in range(METHODS_COUNT):
_http_method.append(
cparser.llhttp_method_name(<cparser.llhttp_method_t> i).decode('ascii'))

assert _aiohttp_method_names[i] is not NULL
_http_method.append(_aiohttp_method_names[i].decode('ascii'))

cdef inline str http_method_str(int i):
if i < METHODS_COUNT:
return <str>_http_method[i]
else:
return "<unknown>"

cdef inline object find_header(bytes raw_header):
cdef Py_ssize_t size
Expand Down Expand Up @@ -506,7 +515,7 @@ cdef class HttpParser:
encoding = enc

if self._cparser.type == cparser.HTTP_REQUEST:
method = http_method_str(self._cparser.method)
method = <str>_http_method[self._cparser.method]
msg = _new_request_message(
method, self._path,
http_version, headers, raw_headers,
Expand Down Expand Up @@ -736,7 +745,7 @@ cdef class HttpRequestParser(HttpParser):
else:
path = self._path[0:idx1]
idx1 += 1
idx2 = self._path.find("#", idx1+1)
idx2 = self._path.find("#", idx1)
if idx2 == -1:
query = self._path[idx1:]
fragment = ""
Expand Down
8 changes: 4 additions & 4 deletions aiohttp/client_middleware_digest_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ class DigestAuthChallenge(TypedDict, total=False):


def escape_quotes(value: str) -> str:
"""Escape double quotes for HTTP header values."""
return value.replace('"', '\\"')
"""Escape backslashes and double quotes for HTTP quoted-strings."""
return value.replace("\\", "\\\\").replace('"', '\\"')


def unescape_quotes(value: str) -> str:
"""Unescape double quotes in HTTP header values."""
return value.replace('\\"', '"')
"""Unescape backslashes and double quotes in HTTP quoted-strings."""
return value.replace('\\"', '"').replace("\\\\", "\\")


def parse_header_pairs(header: str) -> dict[str, str]:
Expand Down
5 changes: 5 additions & 0 deletions aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,11 @@ def body(self) -> payload.Payload:
def skip_auto_headers(self) -> CIMultiDict[None]:
return self._skip_auto_headers or CIMultiDict()

@property
def timeout(self) -> ClientTimeout:
"""The timeout configuration this request runs under (read-only)."""
return self._timeout

@property
def connection_key(self) -> ConnectionKey:
if proxy_headers := self.proxy_headers:
Expand Down
2 changes: 2 additions & 0 deletions aiohttp/web_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ def last_modified(
"%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ceil(value))
)
elif isinstance(value, datetime.datetime):
if value.microsecond:
value = value.replace(microsecond=0) + datetime.timedelta(seconds=1)
self._headers[hdrs.LAST_MODIFIED] = time.strftime(
"%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple()
)
Expand Down
10 changes: 10 additions & 0 deletions docs/client_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2106,6 +2106,16 @@ ClientRequest
- :class:`ssl.SSLContext`: Custom SSL context
- :class:`Fingerprint`: Verify specific certificate fingerprint

.. attribute:: timeout
:type: ClientTimeout

The timeout configuration this request runs under (read-only): the
per-request timeout when one was passed to the request method, the
session's default otherwise. Useful in middleware to bound waits or
retries by the caller's time budget.

.. versionadded:: 3.15

.. attribute:: url
:type: yarl.URL

Expand Down
4 changes: 2 additions & 2 deletions requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ click==8.4.2
# via
# pip-tools
# towncrier
coverage==7.15.1
coverage==7.15.2
# via
# -r requirements/test-common.in
# pytest-cov
Expand All @@ -85,7 +85,7 @@ exceptiongroup==1.3.1
# pytest
execnet==2.1.2
# via pytest-xdist
filelock==3.29.7
filelock==3.30.2
# via
# python-discovery
# virtualenv
Expand Down
4 changes: 2 additions & 2 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ click==8.4.2
# via
# pip-tools
# towncrier
coverage==7.15.1
coverage==7.15.2
# via
# -r requirements/test-common.in
# pytest-cov
Expand All @@ -83,7 +83,7 @@ exceptiongroup==1.3.1
# pytest
execnet==2.1.2
# via pytest-xdist
filelock==3.29.7
filelock==3.30.2
# via
# python-discovery
# virtualenv
Expand Down
2 changes: 1 addition & 1 deletion requirements/lint.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ exceptiongroup==1.3.1
# via
# aiofastnet
# pytest
filelock==3.29.7
filelock==3.30.2
# via
# python-discovery
# virtualenv
Expand Down
2 changes: 1 addition & 1 deletion requirements/test-common-base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ attrs==26.1.0
# via aiohttp
backports-asyncio-runner==1.2.0
# via pytest-asyncio
coverage==7.15.1
coverage==7.15.2
# via pytest-cov
exceptiongroup==1.3.1
# via pytest
Expand Down
2 changes: 1 addition & 1 deletion requirements/test-common.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ blockbuster==1.5.26
# via -r requirements/test-common.in
cffi==2.1.0
# via cryptography
coverage==7.15.1
coverage==7.15.2
# via
# -r requirements/test-common.in
# pytest-cov
Expand Down
2 changes: 1 addition & 1 deletion requirements/test-ft.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ cffi==2.1.0
# via
# cryptography
# pycares
coverage==7.15.1
coverage==7.15.2
# via
# -r requirements/test-common.in
# pytest-cov
Expand Down
2 changes: 1 addition & 1 deletion requirements/test-mobile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ cffi==2.1.0 ; sys_platform != "android" and sys_platform != "ios"
# via
# -r requirements/test-mobile.in
# pycares
coverage==7.15.1
coverage==7.15.2
# via pytest-cov
exceptiongroup==1.3.1
# via
Expand Down
2 changes: 1 addition & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ cffi==2.1.0
# via
# cryptography
# pycares
coverage==7.15.1
coverage==7.15.2
# via
# -r requirements/test-common.in
# pytest-cov
Expand Down
Loading
Loading