One function
static_file in bottlepy/bottle
The author described this change as “fix: If-Modified-Since should be ignored if If-None-Match is present.”. It counts as a record because the check below fails on the code as it stood at 2a743a302 and passes on b73bd1db5, with nothing else changed between the two runs.
Projectbottlepy/bottle
Fix saved2026-07-19
Sharing licenceMIT · LICENSE
Change size+5 −4
What the code was meant to do, written into the code itself as a docstring
Open a file in a safe way and return an instance of :exc:`HTTPResponse` that can be sent back to the client. :param filename: Name or path of the file to send, relative to `root`. :param root: Root path for file lookups. Should be an absolute directory path. :param mimetype: Provide the content-type header (default: guess from file extension) :param download: If True, ask the browser to open a `Save as...` dialog instead of opening the file with the associated program. You can specify a custom filename as a string. If not specified, the original filename is used (default: False). :param charset: The charset for files with a `text/*` mime-type. (default: UTF-8) :param etag: Provide a pre-computed ETag header. If set to `False`, ETag handling is disabled. (default: auto-generate ETag header) :param headers: Additional headers dict to add to the response. While checking user input is always a good idea, this function provides additional protection against malicious `filename` parameters from breaking out of the `root` directory and leaking sensitive information to an attacker. Read-protected files or files outside of the `root` directory are answered with `403 Access Denied`. Missing files result in a `404 Not Found` response. Conditional requests (`If-Modified-Since`, `If-None-Match`) are answered with `304 Not Modified` whenever possible. `HEAD` and `Range` requests (used by download managers to check or continue partial downloads) are also handled automatically.
The change
| 81 | 81 | ||
| 82 | 82 | if etag: | |
| 83 | 83 | headers['ETag'] = etag | |
| 84 | - | check = getenv('HTTP_IF_NONE_MATCH') | |
| 85 | - | if check and check == etag: | |
| 86 | - | return HTTPResponse(status=304, **headers) | |
| 87 | 84 | ||
| 85 | + | inm = getenv('HTTP_IF_NONE_MATCH') | |
| 86 | + | if inm and inm == etag: | |
| 87 | + | return HTTPResponse(status=304, **headers) | |
| 88 | + | ||
| 88 | 89 | ims = getenv('HTTP_IF_MODIFIED_SINCE') | |
| 89 | - | if ims: | |
| 90 | + | if ims and not inm: | |
| 90 | 91 | ims = parse_date(ims.split(";")[0].strip()) | |
| 91 | 92 | if ims is not None and ims >= int(stats.st_mtime): | |
| 92 | 93 | return HTTPResponse(status=304, **headers) |
The check that tells the two apart
fail→pass·test/test_sendfile.py::TestSendFile::test_etag_overrides_ims
Check file test/test_sendfile.py, taken without changes from the fix and copied onto the older code, so the exact same check runs against both versions.
Origin and history
The code before it2a743a302a71460bfe4c0b8b7cb99a306b0328c6
Broken version dated2026-03-23
Modulebottle
Units changedstatic_file
Fingerprint686c6dc6f57b5dbd
Checked2026-08-18 by goldset/0.1
Every field above is generated by our program. None of it is written by hand.