diff --git a/public-api/v1alpha/lib/pipelines_api/logs/get.ex b/public-api/v1alpha/lib/pipelines_api/logs/get.ex index 76888f02e..7b0067e9c 100644 --- a/public-api/v1alpha/lib/pipelines_api/logs/get.ex +++ b/public-api/v1alpha/lib/pipelines_api/logs/get.ex @@ -5,6 +5,8 @@ defmodule PipelinesAPI.Logs.Get do use Plug.Builder + import PipelinesAPI.Util.APIResponse + require Logger alias PipelinesAPI.Pipelines.Common, as: RespCommon alias PipelinesAPI.Util.{Metrics} @@ -49,7 +51,8 @@ defmodule PipelinesAPI.Logs.Get do {:ok, token} -> conn |> put_resp_header("location", build_loghub2_url(conn, job_id, token)) - |> send_resp(conn.status || 302, "") + |> put_status(conn.status || 302) + |> text("") e -> RespCommon.respond(e, conn) @@ -73,7 +76,7 @@ defmodule PipelinesAPI.Logs.Get do end defp prepare_response(events) do - Enum.join(['{ "events": [', Enum.join(events, ","), "] }"], "") + Enum.join([~c'{ "events": [', Enum.join(events, ","), "] }"], "") end defp build_loghub2_url(conn, job_id, token) do diff --git a/public-api/v1alpha/lib/pipelines_api/pipelines/common.ex b/public-api/v1alpha/lib/pipelines_api/pipelines/common.ex index 6d8a2e67f..2605de348 100644 --- a/public-api/v1alpha/lib/pipelines_api/pipelines/common.ex +++ b/public-api/v1alpha/lib/pipelines_api/pipelines/common.ex @@ -2,6 +2,7 @@ defmodule PipelinesAPI.Pipelines.Common do @moduledoc false import Plug.Conn + import PipelinesAPI.Util.APIResponse def respond(state, conn, encode? \\ true) @@ -28,13 +29,17 @@ defmodule PipelinesAPI.Pipelines.Common do # Sobelow is always warning when using send_resp when value is not actual hardcoded string nor encoded, # json encoded content is safe to use send_resp. # sobelow_skip ["XSS.SendResp"] - defp respond_(conn, code, content, _encode? = true), - do: send_resp(conn, code, content |> Poison.encode!()) + defp respond_(conn, code, content, _encode? = true) do + json(conn, content) + end # The Sobelow.XSS.SendResp is focused on Phoenix apps so it does not recognise Plug.HTML.html_escape/1 # sobelow_skip ["XSS.SendResp"] - defp respond_(conn, code, content, _encode? = false), - do: send_resp(conn, code, Plug.HTML.html_escape(content)) + defp respond_(conn, code, content, _encode? = false) do + conn + |> put_status(code) + |> text(Plug.HTML.html_escape(content)) + end def respond_paginated({:error, e}, conn), do: respond({:error, e}, conn) diff --git a/public-api/v1alpha/lib/pipelines_api/router.ex b/public-api/v1alpha/lib/pipelines_api/router.ex index bd241d8ab..40cc6780b 100644 --- a/public-api/v1alpha/lib/pipelines_api/router.ex +++ b/public-api/v1alpha/lib/pipelines_api/router.ex @@ -1,6 +1,8 @@ defmodule PipelinesAPI.Router do use Plug.{Router, ErrorHandler} + import PipelinesAPI.Util.APIResponse + alias PipelinesAPI.Pipelines.Terminate alias PipelinesAPI.Pipelines.DescribeTopology alias PipelinesAPI.Pipelines.PartialRebuild @@ -156,7 +158,7 @@ defmodule PipelinesAPI.Router do match("/logs/:job_id", via: :get, to: PipelinesAPI.Logs.Get) get "/health_check/ping" do - send_resp(conn, 200, "pong") + text(conn, "pong") end # sobelow_skip ["XSS.SendResp"] @@ -167,20 +169,27 @@ defmodule PipelinesAPI.Router do case reason.__struct__ do Plug.Parsers.ParseError -> %{exception: %{message: message}} = reason - send_resp(conn, conn.status, "Malformed request: " <> Plug.HTML.html_escape(message)) + + conn + |> put_status(conn.status) + |> text("Malformed request: " <> Plug.HTML.html_escape(message)) _ -> - send_resp(conn, conn.status, "Something went wrong") + conn + |> put_status(conn.status) + |> text("Something went wrong") end end # Root path has to return 200 OK in order to pass health checks made by ingress # on Kubernets get "/" do - send_resp(conn, 200, "pong") + text(conn, "pong") end match _ do - send_resp(conn, 404, "oops") + conn + |> put_status(404) + |> text("oops") end end diff --git a/public-api/v1alpha/lib/pipelines_api/util/api_response.ex b/public-api/v1alpha/lib/pipelines_api/util/api_response.ex new file mode 100644 index 000000000..0e25327ac --- /dev/null +++ b/public-api/v1alpha/lib/pipelines_api/util/api_response.ex @@ -0,0 +1,46 @@ +defmodule PipelinesAPI.Util.APIResponse do + @moduledoc false + + @doc """ + Sends JSON response. + + + iex> json(conn, %{id: 123}) + + """ + @spec json(Plug.Conn.t(), term) :: Plug.Conn.t() + def json(conn, data) do + content = Poison.encode!(data) + send_resp(conn, conn.status || 200, "application/json", content) + end + + @doc """ + Sends text response. + + ## Examples + + iex> text(conn, "hello") + + iex> text(conn, :implements_to_string) + + """ + @spec text(Plug.Conn.t(), String.Chars.t()) :: Plug.Conn.t() + def text(conn, data) do + send_resp(conn, conn.status || 200, "text/plain", to_string(data)) + end + + defp send_resp(conn, default_status, default_content_type, body) do + conn + |> ensure_resp_content_type(default_content_type) + |> Plug.Conn.send_resp(conn.status || default_status, body) + end + + defp ensure_resp_content_type(%Plug.Conn{resp_headers: resp_headers} = conn, content_type) do + if List.keyfind(resp_headers, "content-type", 0) do + conn + else + content_type = content_type <> "; charset=utf-8" + %Plug.Conn{conn | resp_headers: [{"content-type", content_type} | resp_headers]} + end + end +end