2.0 KiB · text History b868907
defmodule GitGudWeb.PackageIconController do
  @moduledoc """
  A package's icon, for the packages pages.

  The registry already serves the same bytes to F-Droid under
  `/pkg/fdroid/…/icons/`, but that route authenticates with registry
  tokens and knows nothing of a browser session — an `<img>` on a
  private repository's page would get a 401. This gates on the
  signed-in user instead, with the same per-format rule the catalog
  lists packages by, so a card never shows a picture of something the
  viewer couldn't fetch.
  """
  use GitGudWeb, :controller

  alias GitGud.Packages
  alias GitGud.Packages.Package
  alias GitGud.Repositories

  def show(conn, %{"owner" => owner, "name" => name, "id" => id}) do
    user = conn.assigns[:current_scope] && conn.assigns.current_scope.user
    principal = if user, do: {:user, user, :all}, else: :anonymous

    with {:ok, repo} <- fetch_repo(owner, name),
         {id, ""} <- Integer.parse(id),
         %Package{repository_id: rid} = package when rid == repo.id <-
           GitGud.Repo.get(Package, id),
         true <- Packages.Auth.authorize(principal, repo, :read, package.format),
         {:ok, content_type, bytes, sha256} <- Packages.icon(package) do
      etag = ~s("#{sha256}")

      conn =
        conn
        |> put_resp_header("etag", etag)
        # Private: whether it is visible depends on who is asking.
        |> put_resp_header("cache-control", "private, max-age=300")

      if etag in get_req_header(conn, "if-none-match") do
        send_resp(conn, 304, "")
      else
        conn |> put_resp_content_type(content_type, nil) |> send_resp(200, bytes)
      end
    else
      # Missing, forbidden and iconless look alike: an existence oracle
      # for private packages is not what an <img> route is for.
      _ -> conn |> put_status(:not_found) |> text("")
    end
  end

  defp fetch_repo(owner, name) do
    {:ok, Repositories.get_repository_by_path!(owner, name)}
  rescue
    Ecto.NoResultsError -> :error
  end
end