defmodule GitGudWeb.Plugs.PackageUploadParser do
@moduledoc """
Decides who owns the request body for a package-registry upload,
ahead of the endpoint's general `Plug.Parsers`.
Two shapes arrive:
* **multipart** — twine's framing. Parsed here rather than
downstream so it gets its own, much larger, size limit; 8 MB
suits an ordinary form post and not a wheel with a bundled CUDA
runtime.
* **everything else** — Cargo's length-prefixed publish body and
Hex's raw tarball. These are claimed for the controller to read
off the socket. Claiming them matters: Cargo labels its binary
publish body `Content-Type: application/json`, and without this
the endpoint's JSON parser would try to decode a tarball and
fail the request before it ever reached an adapter.
`Plug.Parsers` treats a fetched `body_params` as "someone else
handled this" and no-ops, which is how the claim is made. It does
*not* set `body_params` itself when a body merely passes its `pass`
list, so setting it here is the whole mechanism rather than a
belt-and-braces extra.
"""
@behaviour Plug
# Comfortably past a manylinux wheel with a bundled CUDA runtime.
# Anything genuinely larger belongs in LFS or the OCI registry.
@max_upload_bytes 200_000_000
# Each socket read (1 MB) may take this long. Plug's default, 15s, is a
# 66 KB/s floor: since Bandit 1.14 stopped letting HTTP/2 control frames
# extend a body read, a CI runner pushing a 16 MB APK over a slow link
# got a 408 "Body read timeout" midway. Two minutes is an 8 KB/s floor.
@read_timeout 120_000
@parser_opts Plug.Parsers.init(
parsers: [:multipart],
pass: ["*/*"],
length: @max_upload_bytes,
read_timeout: @read_timeout
)
@doc "The per-read timeout for package upload bodies, for controllers reading them raw."
def read_timeout, do: @read_timeout
@impl true
def init(opts), do: opts
@impl true
def call(%Plug.Conn{path_info: ["pkg" | _], method: method} = conn, _opts)
when method in ["POST", "PUT", "PATCH"] do
if multipart?(conn),
do: Plug.Parsers.call(conn, @parser_opts),
else: %{conn | body_params: %{}}
end
def call(conn, _opts), do: conn
defp multipart?(conn) do
with [value | _] <- Plug.Conn.get_req_header(conn, "content-type"),
{:ok, "multipart", _subtype, _params} <- Plug.Conn.Utils.content_type(value) do
true
else
_ -> false
end
end
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
2.5 KiB · text
History
b868907