Skip to content

Commit caecabf

Browse files
committed
Don't shortcircuit content validation for empty response
Signed-off-by: Judah Rand <17158624+judahrand@users.noreply.github.com>
1 parent 45c62b6 commit caecabf

2 files changed

Lines changed: 98 additions & 6 deletions

File tree

oras/provider.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -539,12 +539,6 @@ def download_blob(
539539
)
540540

541541
os.replace(staged, outfile)
542-
543-
# Allow an empty layer to fail and return /dev/null
544-
except Exception as e:
545-
if digest == oras.defaults.blank_hash:
546-
return os.devnull
547-
raise e
548542
finally:
549543
try:
550544
os.remove(staged)

oras/tests/test_provider.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,104 @@ def test_pull_validates_registered_digest(monkeypatch, tmp_path, algorithm):
163163
assert outfile.read_bytes() == content
164164

165165

166+
def test_download_blob_validates_empty_blob(monkeypatch, tmp_path):
167+
client = oras.provider.Registry(insecure=True)
168+
response = requests.Response()
169+
response.status_code = 200
170+
response._content = b""
171+
response._content_consumed = True
172+
monkeypatch.setattr(client, "get_blob", lambda *args, **kwargs: response)
173+
staged = tmp_path / "staged"
174+
staged.touch()
175+
monkeypatch.setattr(oras.utils, "get_tmpfile", lambda: str(staged))
176+
outfile = tmp_path / "empty"
177+
178+
result = client.download_blob(
179+
"registry.example/repository:tag",
180+
oras.defaults.blank_hash,
181+
str(outfile),
182+
size=0,
183+
)
184+
185+
assert result == str(outfile)
186+
assert outfile.read_bytes() == b""
187+
assert not staged.exists()
188+
189+
190+
@pytest.mark.parametrize("status_code", [401, 404])
191+
def test_download_empty_blob_propagates_http_error(monkeypatch, tmp_path, status_code):
192+
client = oras.provider.Registry(insecure=True)
193+
response = requests.Response()
194+
response.status_code = status_code
195+
response.url = "https://registry.example/v2/repository/blobs/empty"
196+
response._content = b""
197+
response._content_consumed = True
198+
monkeypatch.setattr(client, "get_blob", lambda *args, **kwargs: response)
199+
staged = tmp_path / "staged"
200+
staged.touch()
201+
monkeypatch.setattr(oras.utils, "get_tmpfile", lambda: str(staged))
202+
outfile = tmp_path / "empty"
203+
outfile.write_bytes(b"existing content")
204+
205+
with pytest.raises(requests.HTTPError):
206+
client.download_blob(
207+
"registry.example/repository:tag",
208+
oras.defaults.blank_hash,
209+
str(outfile),
210+
size=0,
211+
)
212+
213+
assert outfile.read_bytes() == b"existing content"
214+
assert not staged.exists()
215+
216+
217+
def test_download_empty_blob_propagates_transport_error(monkeypatch, tmp_path):
218+
client = oras.provider.Registry(insecure=True)
219+
220+
def get_blob(*args, **kwargs):
221+
raise requests.ConnectionError("download failed")
222+
223+
monkeypatch.setattr(client, "get_blob", get_blob)
224+
staged = tmp_path / "staged"
225+
staged.touch()
226+
monkeypatch.setattr(oras.utils, "get_tmpfile", lambda: str(staged))
227+
outfile = tmp_path / "empty"
228+
229+
with pytest.raises(requests.ConnectionError, match="download failed"):
230+
client.download_blob(
231+
"registry.example/repository:tag",
232+
oras.defaults.blank_hash,
233+
str(outfile),
234+
size=0,
235+
)
236+
237+
assert not outfile.exists()
238+
assert not staged.exists()
239+
240+
241+
def test_download_empty_blob_rejects_nonempty_content(monkeypatch, tmp_path):
242+
client = oras.provider.Registry(insecure=True)
243+
response = requests.Response()
244+
response.status_code = 200
245+
response._content = b"not empty"
246+
response._content_consumed = True
247+
monkeypatch.setattr(client, "get_blob", lambda *args, **kwargs: response)
248+
staged = tmp_path / "staged"
249+
staged.touch()
250+
monkeypatch.setattr(oras.utils, "get_tmpfile", lambda: str(staged))
251+
outfile = tmp_path / "empty"
252+
253+
with pytest.raises(ValueError, match="Downloaded blob digest mismatch"):
254+
client.download_blob(
255+
"registry.example/repository:tag",
256+
oras.defaults.blank_hash,
257+
str(outfile),
258+
)
259+
260+
assert not outfile.exists()
261+
assert not staged.exists()
262+
263+
166264
def test_pull_rejects_digest_mismatch_without_replacing_file(monkeypatch, tmp_path):
167265
expected_content = b"expected content"
168266
downloaded_content = b"corrupt! content"

0 commit comments

Comments
 (0)