Skip to content

gh-49288: Reset file-like POST body on HTTP auth retry - #153726

Open
orsenthil wants to merge 5 commits into
python:mainfrom
orsenthil:orsenthil/49288
Open

orsenthil wants to merge 5 commits into
python:mainfrom
orsenthil:orsenthil/49288

Conversation

@orsenthil

@orsenthil orsenthil commented Jul 15, 2026 •

Copy link
Copy Markdown
Member

Fix long-standing bug where HTTP Basic/Digest auth retries send an empty POST body when Request.data is a consumed file-like object (e.g. mmap.mmap, open file, BytesIO).

Fixes gh-49288.

Wrap seekable file-like Request.data in a re-iterator before Basic and
Digest authentication retries so the POST body is resent after a 401.
Non-seekable streams raise ValueError instead of sending an empty body.
@read-the-docs-community

read-the-docs-community Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #34501229 | 📁 Comparing 8bc5f22 against main (1b3914a)

  🔍 Preview build  

165 files changed · + 1 added · ± 164 modified

+ Added

± Modified

@simpleprogrammer2

Copy link
Copy Markdown

@gpshead could you please review this ( I am a new contributor, trying to find best ways to help) ?

@orsenthil

Copy link
Copy Markdown
Member Author

@serhiy-storchaka , would you like to review this?

@ambv ambv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of wrapping req.data, can't we just record where the body starts and seek the original object back there before retrying? That solves the things I'm pointing out in the comments.

Comment thread Lib/urllib/request.py
self._blocksize = blocksize

def __iter__(self):
self._fileobj.seek(0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retry rewinds to offset 0, not to where the body started, so it can send different bytes. Try:

f = BytesIO(b"SKIPME|payload"); f.seek(7)

This is a regression compared to sending an empty response because the latter usually gets rejected.

Comment thread Lib/urllib/request.py
if not getattr(data, 'seekable', lambda: hasattr(data, 'seek'))():
raise ValueError("seekable file-like Request.data is required when "
"the request may be retried (such as HTTP authentication retries).")
request.data = _AuthBodyReiterator(data)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swapping req.data drops the caller's Content-Length and quietly switches the retry to chunked encoding. Chunked encoding is the default, but that means when callers use Content-Length, they mean it, because for instance the server won't take chunked uploads.

Comment thread Lib/urllib/request.py
def __iter__(self):
self._fileobj.seek(0)
while chunk := self._fileobj.read(self._blocksize):
yield chunk

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yielding raw chunks for files open in text-mode can fail. That's not what http.client is doing here.

@bedevere-app

bedevere-app Bot commented Oct 23, 2026

Copy link
Copy Markdown

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@orsenthil

Copy link
Copy Markdown
Member Author

Instead of wrapping req.data, can't we just record where the body starts and seek the original object back there before retrying?

Yup, sounds good.

For the non-seekable body, we will not do a retry. We can retry the 401, so that sender and send with the fresh stream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

urrlib2/httplib doesn't reset file position between requests

3 participants