Skip to content

Commit 6f5c9fa

Browse files
committed
fmt + content-length:0
1 parent 9143c6f commit 6f5c9fa

3 files changed

Lines changed: 56 additions & 10 deletions

File tree

lib/oci/plug/handler.ex

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,16 @@ defmodule OCI.Plug.Handler do
271271
raw_manifest = conn.assigns[:oci_raw_manifest]
272272
manifest_digest = conn.assigns[:oci_digest]
273273

274-
with :ok <- Registry.store_manifest(registry, repo, reference, manifest, raw_manifest, manifest_digest, ctx) do
274+
with :ok <-
275+
Registry.store_manifest(
276+
registry,
277+
repo,
278+
reference,
279+
manifest,
280+
raw_manifest,
281+
manifest_digest,
282+
ctx
283+
) do
275284
conn
276285
|> maybe_set_oci_subject(manifest)
277286
|> put_resp_header("location", Registry.manifests_reference_path(repo, reference))
@@ -334,18 +343,20 @@ defmodule OCI.Plug.Handler do
334343
end
335344

336345
defp maybe_upload_final_chunk(conn, registry, repo, uuid, ctx) do
337-
# The Content-Length header is required, but may be 0 if no final chunk is being uploaded.
338-
conn
339-
|> get_req_header("content-length")
340-
|> List.first()
341-
|> String.to_integer()
342-
|> case do
346+
# The closing PUT may have no body and no Content-Length header when the
347+
# final chunk was uploaded earlier via PATCH (spec: "Content-Length: <length
348+
# of chunk, if present>" and "OPTIONAL: <final chunk byte stream>").
349+
content_length =
350+
case get_req_header(conn, "content-length") do
351+
[val | _] -> String.to_integer(val)
352+
[] -> 0
353+
end
354+
355+
case content_length do
343356
0 ->
344-
# No chunk to upload with final PUT
345357
:ok
346358

347359
_ ->
348-
# Final chunk is included, upload it before completing the blob
349360
case Registry.upload_blob_chunk(
350361
registry,
351362
repo,

lib/oci/registry.ex

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,15 @@ defmodule OCI.Registry do
223223
end
224224
end
225225

226-
def store_manifest(%{storage: storage}, repo, reference, manifest, raw_manifest, manifest_digest, ctx) do
226+
def store_manifest(
227+
%{storage: storage},
228+
repo,
229+
reference,
230+
manifest,
231+
raw_manifest,
232+
manifest_digest,
233+
ctx
234+
) do
227235
required_blobs = referenced_blobs(manifest)
228236

229237
missing =

test/plug_test.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,31 @@ defmodule OCI.PlugTest do
121121
}
122122
end
123123
end
124+
125+
describe "PUT /v2/<name>/blobs/uploads/<uuid> closing PUT" do
126+
test "finalizes a zero-byte blob when Content-Length header is omitted", %{conn: conn} do
127+
open = post(conn, "/myimage/blobs/uploads")
128+
assert open.status == 202
129+
[location] = get_resp_header(open, "location")
130+
uuid = location |> String.split("/") |> List.last()
131+
132+
empty_digest = digest("")
133+
134+
assert empty_digest ==
135+
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
136+
137+
closing =
138+
:put
139+
|> conn("/myimage/blobs/uploads/#{uuid}?digest=#{empty_digest}", nil)
140+
|> Map.put(:script_name, ["v2"])
141+
|> Map.put(:assigns, %{oci_opts: conn.assigns.oci_opts})
142+
|> basic_auth("myuser", "mypass")
143+
|> OCI.Plug.call(conn.assigns.oci_opts)
144+
145+
assert get_req_header(closing, "content-length") == []
146+
assert closing.status == 201
147+
assert [blob_location] = get_resp_header(closing, "location")
148+
assert blob_location == "/v2/myimage/blobs/#{empty_digest}"
149+
end
150+
end
124151
end

0 commit comments

Comments
 (0)